From c74d8f0c4cbe30b326bd4e8985b2068d7bffd76b Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Fri, 20 Dec 2024 19:08:07 +0100 Subject: [PATCH] feat: add swap expiry timestamp fix(clippy): use `clone_if_set` for sync destination fix: revert rebase changes feat: add `expiration_block` to payment details feat: add `blockchain_details` to `GetInfoResponse` fix: rename fields to `_expiry_blockheight` fix: update `GetInfoResponse` structure fix: address review comments taken from https://github.com/breez/breez-sdk-liquid/pull/622#pullrequestreview-2525447533 fix: use better error handling for get_info fix: bindings tests fix: ensure blockchain_info is optional in query --- .../include/breez_sdk_liquid.h | 15 +- .../BreezSDKLiquid/Task/SwapUpdated.swift | 4 +- lib/bindings/src/breez_sdk_liquid.udl | 16 +- lib/bindings/tests/bindings/csharp/Program.cs | 2 +- .../bindings/golang/test_breez_sdk_liquid.go | 2 +- .../tests/bindings/test_breez_liquid_sdk.cs | 2 +- .../tests/bindings/test_breez_sdk_liquid.kts | 2 +- .../tests/bindings/test_breez_sdk_liquid.py | 2 +- .../bindings/test_breez_sdk_liquid.swift | 2 +- lib/core/src/frb_generated.rs | 230 +++++++++++++++--- lib/core/src/model.rs | 38 ++- lib/core/src/persist/cache.rs | 50 +++- lib/core/src/persist/migrations.rs | 4 + lib/core/src/persist/mod.rs | 112 +++++---- lib/core/src/persist/receive.rs | 28 ++- lib/core/src/persist/send.rs | 28 ++- lib/core/src/recover/recoverer.rs | 19 +- lib/core/src/sdk.rs | 59 +++-- lib/core/src/sync/model/data.rs | 13 +- lib/core/src/test_utils/persist.rs | 2 + lib/core/src/test_utils/sync.rs | 2 + packages/dart/lib/src/frb_generated.dart | 129 +++++++--- packages/dart/lib/src/frb_generated.io.dart | 71 +++++- packages/dart/lib/src/model.dart | 116 ++++++--- packages/dart/lib/src/model.freezed.dart | 81 +++++- ...utter_breez_liquid_bindings_generated.dart | 23 +- .../breezsdkliquid/BreezSDKLiquidMapper.kt | 132 ++++++++-- .../ios/BreezSDKLiquidMapper.swift | 129 ++++++++-- packages/react-native/src/index.ts | 23 +- 29 files changed, 1053 insertions(+), 283 deletions(-) diff --git a/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h b/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h index 5a6eafade..19fc1b882 100644 --- a/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h +++ b/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h @@ -474,6 +474,7 @@ typedef struct wire_cst_ln_url_info { typedef struct wire_cst_PaymentDetails_Lightning { struct wire_cst_list_prim_u_8_strict *swap_id; struct wire_cst_list_prim_u_8_strict *description; + uint32_t liquid_expiration_blockheight; struct wire_cst_list_prim_u_8_strict *preimage; struct wire_cst_list_prim_u_8_strict *bolt11; struct wire_cst_list_prim_u_8_strict *bolt12_offer; @@ -491,6 +492,8 @@ typedef struct wire_cst_PaymentDetails_Liquid { typedef struct wire_cst_PaymentDetails_Bitcoin { struct wire_cst_list_prim_u_8_strict *swap_id; struct wire_cst_list_prim_u_8_strict *description; + uint32_t *liquid_expiration_blockheight; + uint32_t *bitcoin_expiration_blockheight; struct wire_cst_list_prim_u_8_strict *refund_tx_id; uint64_t *refund_tx_amount_sat; } wire_cst_PaymentDetails_Bitcoin; @@ -695,16 +698,26 @@ typedef struct wire_cst_list_refundable_swap { int32_t len; } wire_cst_list_refundable_swap; +typedef struct wire_cst_blockchain_info { + uint32_t liquid_tip; + uint32_t bitcoin_tip; +} wire_cst_blockchain_info; + typedef struct wire_cst_check_message_response { bool is_valid; } wire_cst_check_message_response; -typedef struct wire_cst_get_info_response { +typedef struct wire_cst_wallet_info { uint64_t balance_sat; uint64_t pending_send_sat; uint64_t pending_receive_sat; struct wire_cst_list_prim_u_8_strict *fingerprint; struct wire_cst_list_prim_u_8_strict *pubkey; +} wire_cst_wallet_info; + +typedef struct wire_cst_get_info_response { + struct wire_cst_wallet_info wallet_info; + struct wire_cst_blockchain_info blockchain_info; } wire_cst_get_info_response; typedef struct wire_cst_InputType_BitcoinAddress { diff --git a/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift b/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift index 26a74d2e2..6b6e649e3 100644 --- a/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift +++ b/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift @@ -59,9 +59,9 @@ class SwapUpdatedTask : TaskProtocol { func getSwapId(details: PaymentDetails?) -> String? { if let details = details { switch details { - case let .bitcoin(swapId, _, _, _): + case let .bitcoin(swapId, _, _, _, _, _): return swapId - case let .lightning(swapId, _, _, _, _, _, _, _, _): + case let .lightning(swapId, _, _, _, _, _, _, _, _, _): return swapId default: break diff --git a/lib/bindings/src/breez_sdk_liquid.udl b/lib/bindings/src/breez_sdk_liquid.udl index 6a63085eb..e0bb2d0cf 100644 --- a/lib/bindings/src/breez_sdk_liquid.udl +++ b/lib/bindings/src/breez_sdk_liquid.udl @@ -355,7 +355,12 @@ dictionary ConnectWithSignerRequest { Config config; }; -dictionary GetInfoResponse { +dictionary BlockchainInfo { + u32 liquid_tip; + u32 bitcoin_tip; +}; + +dictionary WalletInfo { u64 balance_sat; u64 pending_send_sat; u64 pending_receive_sat; @@ -363,6 +368,11 @@ dictionary GetInfoResponse { string pubkey; }; +dictionary GetInfoResponse { + WalletInfo wallet_info; + BlockchainInfo blockchain_info; +}; + dictionary SignMessageRequest { string message; }; @@ -569,9 +579,9 @@ dictionary LnUrlInfo { [Enum] interface PaymentDetails { - Lightning(string swap_id, string description, string? preimage, string? bolt11, string? bolt12_offer, string? payment_hash, LnUrlInfo? lnurl_info, string? refund_tx_id, u64? refund_tx_amount_sat); + Lightning(string swap_id, string description, u32 liquid_expiration_blockheight, string? preimage, string? bolt11, string? bolt12_offer, string? payment_hash, LnUrlInfo? lnurl_info, string? refund_tx_id, u64? refund_tx_amount_sat); Liquid(string destination, string description); - Bitcoin(string swap_id, string description, string? refund_tx_id, u64? refund_tx_amount_sat); + Bitcoin(string swap_id, string description, u32? bitcoin_expiration_blockheight, u32? liquid_expiration_blockheight, string? refund_tx_id, u64? refund_tx_amount_sat); }; dictionary Payment { diff --git a/lib/bindings/tests/bindings/csharp/Program.cs b/lib/bindings/tests/bindings/csharp/Program.cs index 6662d9147..96e3ea5b4 100644 --- a/lib/bindings/tests/bindings/csharp/Program.cs +++ b/lib/bindings/tests/bindings/csharp/Program.cs @@ -11,7 +11,7 @@ GetInfoResponse? info = sdk.GetInfo(); - Console.WriteLine(info!.pubkey); + Console.WriteLine(info!.walletInfo.pubkey); } catch (Exception e) { diff --git a/lib/bindings/tests/bindings/golang/test_breez_sdk_liquid.go b/lib/bindings/tests/bindings/golang/test_breez_sdk_liquid.go index 0e066daae..aa5e5ee35 100644 --- a/lib/bindings/tests/bindings/golang/test_breez_sdk_liquid.go +++ b/lib/bindings/tests/bindings/golang/test_breez_sdk_liquid.go @@ -29,5 +29,5 @@ func main() { log.Fatalf("GetInfo failed: %#v", err) } - log.Print(info.Pubkey) + log.Print(info.WalletInfo.Pubkey) } diff --git a/lib/bindings/tests/bindings/test_breez_liquid_sdk.cs b/lib/bindings/tests/bindings/test_breez_liquid_sdk.cs index 0e45caaec..8f7afdd46 100644 --- a/lib/bindings/tests/bindings/test_breez_liquid_sdk.cs +++ b/lib/bindings/tests/bindings/test_breez_liquid_sdk.cs @@ -11,7 +11,7 @@ GetInfoResponse? info = sdk.GetInfo(); - Console.WriteLine(info!.pubkey); + Console.WriteLine(info!.walletInfo.pubkey); } catch (Exception e) { diff --git a/lib/bindings/tests/bindings/test_breez_sdk_liquid.kts b/lib/bindings/tests/bindings/test_breez_sdk_liquid.kts index 807202faa..4dae1da08 100644 --- a/lib/bindings/tests/bindings/test_breez_sdk_liquid.kts +++ b/lib/bindings/tests/bindings/test_breez_sdk_liquid.kts @@ -18,7 +18,7 @@ try { sdk.removeEventListener(listenerId) println("$nodeInfo") - assert(nodeInfo.pubkey.equals("03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494")) + assert(nodeInfo.walletInfo.pubkey.equals("03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494")) } catch (ex: Exception) { throw RuntimeException(ex.toString()) } diff --git a/lib/bindings/tests/bindings/test_breez_sdk_liquid.py b/lib/bindings/tests/bindings/test_breez_sdk_liquid.py index 83a0decc4..f8a2851e7 100644 --- a/lib/bindings/tests/bindings/test_breez_sdk_liquid.py +++ b/lib/bindings/tests/bindings/test_breez_sdk_liquid.py @@ -19,6 +19,6 @@ def test(): sdk.remove_event_listener(listener_id) print(node_info) - assert node_info.pubkey == "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494" + assert node_info.wallet_info.pubkey == "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494" test() diff --git a/lib/bindings/tests/bindings/test_breez_sdk_liquid.swift b/lib/bindings/tests/bindings/test_breez_sdk_liquid.swift index b5faa7fb6..81ac2517b 100644 --- a/lib/bindings/tests/bindings/test_breez_sdk_liquid.swift +++ b/lib/bindings/tests/bindings/test_breez_sdk_liquid.swift @@ -18,4 +18,4 @@ let nodeInfo = try sdk.getInfo(); try sdk.removeEventListener(id: listenerId); print(nodeInfo); -assert(nodeInfo.pubkey == "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494", "nodeInfo.pubkey"); +assert(nodeInfo.walletInfo.pubkey == "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494", "nodeInfo.pubkey"); diff --git a/lib/core/src/frb_generated.rs b/lib/core/src/frb_generated.rs index 0f6799c8a..5f51cce1f 100644 --- a/lib/core/src/frb_generated.rs +++ b/lib/core/src/frb_generated.rs @@ -2307,6 +2307,18 @@ impl SseDecode for crate::bindings::BitcoinAddressData { } } +impl SseDecode for crate::model::BlockchainInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_liquidTip = ::sse_decode(deserializer); + let mut var_bitcoinTip = ::sse_decode(deserializer); + return crate::model::BlockchainInfo { + liquid_tip: var_liquidTip, + bitcoin_tip: var_bitcoinTip, + }; + } +} + impl SseDecode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -2470,17 +2482,11 @@ impl SseDecode for crate::bindings::FiatCurrency { impl SseDecode for crate::model::GetInfoResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut var_balanceSat = ::sse_decode(deserializer); - let mut var_pendingSendSat = ::sse_decode(deserializer); - let mut var_pendingReceiveSat = ::sse_decode(deserializer); - let mut var_fingerprint = ::sse_decode(deserializer); - let mut var_pubkey = ::sse_decode(deserializer); + let mut var_walletInfo = ::sse_decode(deserializer); + let mut var_blockchainInfo = ::sse_decode(deserializer); return crate::model::GetInfoResponse { - balance_sat: var_balanceSat, - pending_send_sat: var_pendingSendSat, - pending_receive_sat: var_pendingReceiveSat, - fingerprint: var_fingerprint, - pubkey: var_pubkey, + wallet_info: var_walletInfo, + blockchain_info: var_blockchainInfo, }; } } @@ -3624,6 +3630,7 @@ impl SseDecode for crate::model::PaymentDetails { 0 => { let mut var_swapId = ::sse_decode(deserializer); let mut var_description = ::sse_decode(deserializer); + let mut var_liquidExpirationBlockheight = ::sse_decode(deserializer); let mut var_preimage = >::sse_decode(deserializer); let mut var_bolt11 = >::sse_decode(deserializer); let mut var_bolt12Offer = >::sse_decode(deserializer); @@ -3634,6 +3641,7 @@ impl SseDecode for crate::model::PaymentDetails { return crate::model::PaymentDetails::Lightning { swap_id: var_swapId, description: var_description, + liquid_expiration_blockheight: var_liquidExpirationBlockheight, preimage: var_preimage, bolt11: var_bolt11, bolt12_offer: var_bolt12Offer, @@ -3654,11 +3662,15 @@ impl SseDecode for crate::model::PaymentDetails { 2 => { let mut var_swapId = ::sse_decode(deserializer); let mut var_description = ::sse_decode(deserializer); + let mut var_liquidExpirationBlockheight = >::sse_decode(deserializer); + let mut var_bitcoinExpirationBlockheight = >::sse_decode(deserializer); let mut var_refundTxId = >::sse_decode(deserializer); let mut var_refundTxAmountSat = >::sse_decode(deserializer); return crate::model::PaymentDetails::Bitcoin { swap_id: var_swapId, description: var_description, + liquid_expiration_blockheight: var_liquidExpirationBlockheight, + bitcoin_expiration_blockheight: var_bitcoinExpirationBlockheight, refund_tx_id: var_refundTxId, refund_tx_amount_sat: var_refundTxAmountSat, }; @@ -4386,6 +4398,24 @@ impl SseDecode for usize { } } +impl SseDecode for crate::model::WalletInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_balanceSat = ::sse_decode(deserializer); + let mut var_pendingSendSat = ::sse_decode(deserializer); + let mut var_pendingReceiveSat = ::sse_decode(deserializer); + let mut var_fingerprint = ::sse_decode(deserializer); + let mut var_pubkey = ::sse_decode(deserializer); + return crate::model::WalletInfo { + balance_sat: var_balanceSat, + pending_send_sat: var_pendingSendSat, + pending_receive_sat: var_pendingReceiveSat, + fingerprint: var_fingerprint, + pubkey: var_pubkey, + }; + } +} + fn pde_ffi_dispatcher_primary_impl( func_id: i32, port: flutter_rust_bridge::for_generated::MessagePort, @@ -4587,6 +4617,24 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.liquid_tip.into_into_dart().into_dart(), + self.bitcoin_tip.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::BlockchainInfo {} +impl flutter_rust_bridge::IntoIntoDart + for crate::model::BlockchainInfo +{ + fn into_into_dart(self) -> crate::model::BlockchainInfo { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::model::BuyBitcoinProvider { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { @@ -4791,11 +4839,8 @@ impl flutter_rust_bridge::IntoIntoDart impl flutter_rust_bridge::IntoDart for crate::model::GetInfoResponse { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ - self.balance_sat.into_into_dart().into_dart(), - self.pending_send_sat.into_into_dart().into_dart(), - self.pending_receive_sat.into_into_dart().into_dart(), - self.fingerprint.into_into_dart().into_dart(), - self.pubkey.into_into_dart().into_dart(), + self.wallet_info.into_into_dart().into_dart(), + self.blockchain_info.into_into_dart().into_dart(), ] .into_dart() } @@ -5703,6 +5748,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentDetails { crate::model::PaymentDetails::Lightning { swap_id, description, + liquid_expiration_blockheight, preimage, bolt11, bolt12_offer, @@ -5714,6 +5760,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentDetails { 0.into_dart(), swap_id.into_into_dart().into_dart(), description.into_into_dart().into_dart(), + liquid_expiration_blockheight.into_into_dart().into_dart(), preimage.into_into_dart().into_dart(), bolt11.into_into_dart().into_dart(), bolt12_offer.into_into_dart().into_dart(), @@ -5735,12 +5782,16 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentDetails { crate::model::PaymentDetails::Bitcoin { swap_id, description, + liquid_expiration_blockheight, + bitcoin_expiration_blockheight, refund_tx_id, refund_tx_amount_sat, } => [ 2.into_dart(), swap_id.into_into_dart().into_dart(), description.into_into_dart().into_dart(), + liquid_expiration_blockheight.into_into_dart().into_dart(), + bitcoin_expiration_blockheight.into_into_dart().into_dart(), refund_tx_id.into_into_dart().into_dart(), refund_tx_amount_sat.into_into_dart().into_dart(), ] @@ -6604,6 +6655,25 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.balance_sat.into_into_dart().into_dart(), + self.pending_send_sat.into_into_dart().into_dart(), + self.pending_receive_sat.into_into_dart().into_dart(), + self.fingerprint.into_into_dart().into_dart(), + self.pubkey.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::WalletInfo {} +impl flutter_rust_bridge::IntoIntoDart for crate::model::WalletInfo { + fn into_into_dart(self) -> crate::model::WalletInfo { + self + } +} impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6739,6 +6809,14 @@ impl SseEncode for crate::bindings::BitcoinAddressData { } } +impl SseEncode for crate::model::BlockchainInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.liquid_tip, serializer); + ::sse_encode(self.bitcoin_tip, serializer); + } +} + impl SseEncode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -6856,11 +6934,8 @@ impl SseEncode for crate::bindings::FiatCurrency { impl SseEncode for crate::model::GetInfoResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - ::sse_encode(self.balance_sat, serializer); - ::sse_encode(self.pending_send_sat, serializer); - ::sse_encode(self.pending_receive_sat, serializer); - ::sse_encode(self.fingerprint, serializer); - ::sse_encode(self.pubkey, serializer); + ::sse_encode(self.wallet_info, serializer); + ::sse_encode(self.blockchain_info, serializer); } } @@ -7765,6 +7840,7 @@ impl SseEncode for crate::model::PaymentDetails { crate::model::PaymentDetails::Lightning { swap_id, description, + liquid_expiration_blockheight, preimage, bolt11, bolt12_offer, @@ -7776,6 +7852,7 @@ impl SseEncode for crate::model::PaymentDetails { ::sse_encode(0, serializer); ::sse_encode(swap_id, serializer); ::sse_encode(description, serializer); + ::sse_encode(liquid_expiration_blockheight, serializer); >::sse_encode(preimage, serializer); >::sse_encode(bolt11, serializer); >::sse_encode(bolt12_offer, serializer); @@ -7795,12 +7872,16 @@ impl SseEncode for crate::model::PaymentDetails { crate::model::PaymentDetails::Bitcoin { swap_id, description, + liquid_expiration_blockheight, + bitcoin_expiration_blockheight, refund_tx_id, refund_tx_amount_sat, } => { ::sse_encode(2, serializer); ::sse_encode(swap_id, serializer); ::sse_encode(description, serializer); + >::sse_encode(liquid_expiration_blockheight, serializer); + >::sse_encode(bitcoin_expiration_blockheight, serializer); >::sse_encode(refund_tx_id, serializer); >::sse_encode(refund_tx_amount_sat, serializer); } @@ -8377,6 +8458,17 @@ impl SseEncode for usize { } } +impl SseEncode for crate::model::WalletInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.balance_sat, serializer); + ::sse_encode(self.pending_send_sat, serializer); + ::sse_encode(self.pending_receive_sat, serializer); + ::sse_encode(self.fingerprint, serializer); + ::sse_encode(self.pubkey, serializer); + } +} + #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. @@ -8557,6 +8649,15 @@ mod io { } } } + impl CstDecode for wire_cst_blockchain_info { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::BlockchainInfo { + crate::model::BlockchainInfo { + liquid_tip: self.liquid_tip.cst_decode(), + bitcoin_tip: self.bitcoin_tip.cst_decode(), + } + } + } impl CstDecode for *mut wire_cst_aes_success_action_data { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::bindings::AesSuccessActionData { @@ -9022,11 +9123,8 @@ mod io { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::GetInfoResponse { crate::model::GetInfoResponse { - balance_sat: self.balance_sat.cst_decode(), - pending_send_sat: self.pending_send_sat.cst_decode(), - pending_receive_sat: self.pending_receive_sat.cst_decode(), - fingerprint: self.fingerprint.cst_decode(), - pubkey: self.pubkey.cst_decode(), + wallet_info: self.wallet_info.cst_decode(), + blockchain_info: self.blockchain_info.cst_decode(), } } } @@ -9789,6 +9887,9 @@ mod io { crate::model::PaymentDetails::Lightning { swap_id: ans.swap_id.cst_decode(), description: ans.description.cst_decode(), + liquid_expiration_blockheight: ans + .liquid_expiration_blockheight + .cst_decode(), preimage: ans.preimage.cst_decode(), bolt11: ans.bolt11.cst_decode(), bolt12_offer: ans.bolt12_offer.cst_decode(), @@ -9810,6 +9911,12 @@ mod io { crate::model::PaymentDetails::Bitcoin { swap_id: ans.swap_id.cst_decode(), description: ans.description.cst_decode(), + liquid_expiration_blockheight: ans + .liquid_expiration_blockheight + .cst_decode(), + bitcoin_expiration_blockheight: ans + .bitcoin_expiration_blockheight + .cst_decode(), refund_tx_id: ans.refund_tx_id.cst_decode(), refund_tx_amount_sat: ans.refund_tx_amount_sat.cst_decode(), } @@ -10321,6 +10428,18 @@ mod io { } } } + impl CstDecode for wire_cst_wallet_info { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::WalletInfo { + crate::model::WalletInfo { + balance_sat: self.balance_sat.cst_decode(), + pending_send_sat: self.pending_send_sat.cst_decode(), + pending_receive_sat: self.pending_receive_sat.cst_decode(), + fingerprint: self.fingerprint.cst_decode(), + pubkey: self.pubkey.cst_decode(), + } + } + } impl NewWithNullPtr for wire_cst_aes_success_action_data { fn new_with_null_ptr() -> Self { Self { @@ -10414,6 +10533,19 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_blockchain_info { + fn new_with_null_ptr() -> Self { + Self { + liquid_tip: Default::default(), + bitcoin_tip: Default::default(), + } + } + } + impl Default for wire_cst_blockchain_info { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_buy_bitcoin_request { fn new_with_null_ptr() -> Self { Self { @@ -10539,11 +10671,8 @@ mod io { impl NewWithNullPtr for wire_cst_get_info_response { fn new_with_null_ptr() -> Self { Self { - balance_sat: Default::default(), - pending_send_sat: Default::default(), - pending_receive_sat: Default::default(), - fingerprint: core::ptr::null_mut(), - pubkey: core::ptr::null_mut(), + wallet_info: Default::default(), + blockchain_info: Default::default(), } } } @@ -11516,6 +11645,22 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_wallet_info { + fn new_with_null_ptr() -> Self { + Self { + balance_sat: Default::default(), + pending_send_sat: Default::default(), + pending_receive_sat: Default::default(), + fingerprint: core::ptr::null_mut(), + pubkey: core::ptr::null_mut(), + } + } + } + impl Default for wire_cst_wallet_info { + fn default() -> Self { + Self::new_with_null_ptr() + } + } #[no_mangle] pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener( @@ -12518,6 +12663,12 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_blockchain_info { + liquid_tip: u32, + bitcoin_tip: u32, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_buy_bitcoin_request { prepare_response: wire_cst_prepare_buy_bitcoin_response, redirect_url: *mut wire_cst_list_prim_u_8_strict, @@ -12585,11 +12736,8 @@ mod io { #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_get_info_response { - balance_sat: u64, - pending_send_sat: u64, - pending_receive_sat: u64, - fingerprint: *mut wire_cst_list_prim_u_8_strict, - pubkey: *mut wire_cst_list_prim_u_8_strict, + wallet_info: wire_cst_wallet_info, + blockchain_info: wire_cst_blockchain_info, } #[repr(C)] #[derive(Clone, Copy)] @@ -13242,6 +13390,7 @@ mod io { pub struct wire_cst_PaymentDetails_Lightning { swap_id: *mut wire_cst_list_prim_u_8_strict, description: *mut wire_cst_list_prim_u_8_strict, + liquid_expiration_blockheight: u32, preimage: *mut wire_cst_list_prim_u_8_strict, bolt11: *mut wire_cst_list_prim_u_8_strict, bolt12_offer: *mut wire_cst_list_prim_u_8_strict, @@ -13261,6 +13410,8 @@ mod io { pub struct wire_cst_PaymentDetails_Bitcoin { swap_id: *mut wire_cst_list_prim_u_8_strict, description: *mut wire_cst_list_prim_u_8_strict, + liquid_expiration_blockheight: *mut u32, + bitcoin_expiration_blockheight: *mut u32, refund_tx_id: *mut wire_cst_list_prim_u_8_strict, refund_tx_amount_sat: *mut u64, } @@ -13687,6 +13838,15 @@ mod io { url: *mut wire_cst_list_prim_u_8_strict, matches_callback_domain: bool, } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_wallet_info { + balance_sat: u64, + pending_send_sat: u64, + pending_receive_sat: u64, + fingerprint: *mut wire_cst_list_prim_u_8_strict, + pubkey: *mut wire_cst_list_prim_u_8_strict, + } } #[cfg(not(target_family = "wasm"))] pub use io::*; diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 1d0f55b84..4fe29be14 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -540,9 +540,14 @@ pub struct RefundResponse { pub refund_tx_id: String, } -/// Returned when calling [crate::sdk::LiquidSdk::get_info]. +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct BlockchainInfo { + pub liquid_tip: u32, + pub bitcoin_tip: u32, +} + #[derive(Debug, Serialize, Deserialize)] -pub struct GetInfoResponse { +pub struct WalletInfo { /// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`. pub balance_sat: u64, /// Amount that is being used for ongoing Send swaps @@ -555,6 +560,16 @@ pub struct GetInfoResponse { pub pubkey: String, } +/// Returned when calling [crate::sdk::LiquidSdk::get_info]. +#[derive(Debug, Serialize, Deserialize)] +pub struct GetInfoResponse { + /// The wallet information, such as the balance, fingerprint and public key + pub wallet_info: WalletInfo, + /// The latest synced blockchain information, such as the Liquid/Bitcoin tips + #[serde(default)] + pub blockchain_info: BlockchainInfo, +} + /// An argument when calling [crate::sdk::LiquidSdk::sign_message]. #[derive(Clone, Debug, PartialEq)] pub struct SignMessageRequest { @@ -908,6 +923,7 @@ pub(crate) struct SendSwap { /// Persisted as soon as a refund tx is broadcast pub(crate) refund_tx_id: Option, pub(crate) created_at: u32, + pub(crate) timeout_block_height: u64, pub(crate) state: PaymentState, pub(crate) refund_private_key: String, } @@ -1001,6 +1017,7 @@ pub(crate) struct ReceiveSwap { /// Until the lockup tx is seen in the mempool, it contains the swap creation time. /// Afterwards, it shows the lockup tx creation time. pub(crate) created_at: u32, + pub(crate) timeout_block_height: u32, pub(crate) state: PaymentState, } impl ReceiveSwap { @@ -1164,6 +1181,7 @@ pub enum PaymentState { /// [prepare_refund](crate::sdk::LiquidSdk::prepare_refund)/[refund](crate::sdk::LiquidSdk::refund). WaitingFeeAcceptance = 7, } + impl ToSql for PaymentState { fn to_sql(&self) -> rusqlite::Result> { Ok(rusqlite::types::ToSqlOutput::from(*self as i8)) @@ -1294,6 +1312,9 @@ pub struct PaymentSwapData { /// Swap creation timestamp pub created_at: u32, + /// The height of the block at which the swap will no longer be valid + pub expiration_blockheight: u32, + pub preimage: Option, pub bolt11: Option, pub bolt12_offer: Option, @@ -1344,7 +1365,10 @@ pub enum PaymentDetails { /// Represents the invoice description description: String, - /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). + /// The height of the block at which the swap will no longer be valid + liquid_expiration_blockheight: u32, + + /// The preimage of the paid invoice (proof of payment). preimage: Option, /// Represents the Bolt11 invoice associated with a payment @@ -1381,6 +1405,14 @@ pub enum PaymentDetails { /// Represents the invoice description description: String, + /// The height of the Liquid block at which the swap will no longer be valid + /// It should always be populated in case of an outgoing chain swap + liquid_expiration_blockheight: Option, + + /// The height of the Bitcoin block at which the swap will no longer be valid + /// It should always be populated in case of an incoming chain swap + bitcoin_expiration_blockheight: Option, + /// For a Send swap which was refunded, this is the refund tx id refund_tx_id: Option, diff --git a/lib/core/src/persist/cache.rs b/lib/core/src/persist/cache.rs index 6f04e40ec..17a5c6966 100644 --- a/lib/core/src/persist/cache.rs +++ b/lib/core/src/persist/cache.rs @@ -1,13 +1,14 @@ use anyhow::Result; -use rusqlite::{Transaction, TransactionBehavior}; +use rusqlite::{OptionalExtension, Transaction, TransactionBehavior}; use std::str::FromStr; use crate::model::GetInfoResponse; use crate::sync::model::{data::LAST_DERIVATION_INDEX_DATA_ID, RecordType}; -use super::Persister; +use super::{BlockchainInfo, Persister, WalletInfo}; const KEY_WALLET_INFO: &str = "wallet_info"; +const KEY_BLOCKCHAIN_INFO: &str = "blockchain_info"; const KEY_SWAPPER_PROXY_URL: &str = "swapper_proxy_url"; const KEY_IS_FIRST_SYNC_COMPLETE: &str = "is_first_sync_complete"; const KEY_WEBHOOK_URL: &str = "webhook_url"; @@ -64,17 +65,48 @@ impl Persister { res } - pub fn set_wallet_info(&self, info: &GetInfoResponse) -> Result<()> { + pub fn set_wallet_info(&self, info: &WalletInfo) -> Result<()> { let serialized_info = serde_json::to_string(info)?; self.update_cached_item(KEY_WALLET_INFO, serialized_info) } - pub fn get_wallet_info(&self) -> Result> { - let info_str = self.get_cached_item(KEY_WALLET_INFO)?; - Ok(match info_str { - Some(str) => serde_json::from_str(str.as_str())?, - None => None, - }) + pub fn set_blockchain_info(&self, info: &BlockchainInfo) -> Result<()> { + let serialized_info = serde_json::to_string(info)?; + self.update_cached_item(KEY_BLOCKCHAIN_INFO, serialized_info) + } + + pub fn get_info(&self) -> Result> { + let con = self.get_connection()?; + + let info: Option<(Option, Option)> = con + .query_row( + &format!( + " + SELECT + c1.value AS wallet_info, + COALESCE(c2.value, NULL) AS blockchain_info + FROM (SELECT value FROM cached_items WHERE key = '{KEY_WALLET_INFO}') c1 + LEFT JOIN (SELECT value FROM cached_items WHERE key = '{KEY_BLOCKCHAIN_INFO}') c2 + " + ), + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .optional()?; + + match info { + Some((Some(wallet_info), blockchain_info)) => { + let wallet_info = serde_json::from_str(&wallet_info)?; + let blockchain_info = blockchain_info + .and_then(|info| serde_json::from_str(&info).ok()) + .unwrap_or_default(); + Ok(Some(GetInfoResponse { + wallet_info, + blockchain_info, + })) + } + _ => Ok(None), + } } pub fn set_swapper_proxy_url(&self, swapper_proxy_url: String) -> Result<()> { diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index 87a772d29..07f9a6c81 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -217,5 +217,9 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { "ALTER TABLE payment_tx_data ADD COLUMN unblinding_data TEXT;", "ALTER TABLE chain_swaps ADD COLUMN actual_payer_amount_sat INTEGER;", "ALTER TABLE chain_swaps ADD COLUMN accepted_receiver_amount_sat INTEGER;", + " + ALTER TABLE receive_swaps ADD COLUMN timeout_block_height INTEGER NOT NULL DEFAULT 0; + ALTER TABLE send_swaps ADD COLUMN timeout_block_height INTEGER NOT NULL DEFAULT 0; + ", ] } diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 15cee000a..a5952452f 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -369,6 +369,7 @@ impl Persister { ptx.unblinding_data, rs.id, rs.created_at, + rs.timeout_block_height, rs.invoice, rs.payment_hash, rs.description, @@ -379,6 +380,7 @@ impl Persister { rs.pair_fees_json, ss.id, ss.created_at, + ss.timeout_block_height, ss.invoice, ss.bolt12_offer, ss.payment_hash, @@ -391,6 +393,7 @@ impl Persister { ss.pair_fees_json, cs.id, cs.created_at, + cs.timeout_block_height, cs.direction, cs.preimage, cs.description, @@ -460,53 +463,56 @@ impl Persister { let maybe_receive_swap_id: Option = row.get(7)?; let maybe_receive_swap_created_at: Option = row.get(8)?; - let maybe_receive_swap_invoice: Option = row.get(9)?; - let maybe_receive_swap_payment_hash: Option = row.get(10)?; - let maybe_receive_swap_description: Option = row.get(11)?; - let maybe_receive_swap_preimage: Option = row.get(12)?; - let maybe_receive_swap_payer_amount_sat: Option = row.get(13)?; - let maybe_receive_swap_receiver_amount_sat: Option = row.get(14)?; - let maybe_receive_swap_receiver_state: Option = row.get(15)?; - let maybe_receive_swap_pair_fees_json: Option = row.get(16)?; + let maybe_receive_swap_timeout_block_height: Option = row.get(9)?; + let maybe_receive_swap_invoice: Option = row.get(10)?; + let maybe_receive_swap_payment_hash: Option = row.get(11)?; + let maybe_receive_swap_description: Option = row.get(12)?; + let maybe_receive_swap_preimage: Option = row.get(13)?; + let maybe_receive_swap_payer_amount_sat: Option = row.get(14)?; + let maybe_receive_swap_receiver_amount_sat: Option = row.get(15)?; + let maybe_receive_swap_receiver_state: Option = row.get(16)?; + let maybe_receive_swap_pair_fees_json: Option = row.get(17)?; let maybe_receive_swap_pair_fees: Option = maybe_receive_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok()); - let maybe_send_swap_id: Option = row.get(17)?; - let maybe_send_swap_created_at: Option = row.get(18)?; - let maybe_send_swap_invoice: Option = row.get(19)?; - let maybe_send_swap_bolt12_offer: Option = row.get(20)?; - let maybe_send_swap_payment_hash: Option = row.get(21)?; - let maybe_send_swap_description: Option = row.get(22)?; - let maybe_send_swap_preimage: Option = row.get(23)?; - let maybe_send_swap_refund_tx_id: Option = row.get(24)?; - let maybe_send_swap_payer_amount_sat: Option = row.get(25)?; - let maybe_send_swap_receiver_amount_sat: Option = row.get(26)?; - let maybe_send_swap_state: Option = row.get(27)?; - let maybe_send_swap_pair_fees_json: Option = row.get(28)?; + let maybe_send_swap_id: Option = row.get(18)?; + let maybe_send_swap_created_at: Option = row.get(19)?; + let maybe_send_swap_timeout_block_height: Option = row.get(20)?; + let maybe_send_swap_invoice: Option = row.get(21)?; + let maybe_send_swap_bolt12_offer: Option = row.get(22)?; + let maybe_send_swap_payment_hash: Option = row.get(23)?; + let maybe_send_swap_description: Option = row.get(24)?; + let maybe_send_swap_preimage: Option = row.get(25)?; + let maybe_send_swap_refund_tx_id: Option = row.get(26)?; + let maybe_send_swap_payer_amount_sat: Option = row.get(27)?; + let maybe_send_swap_receiver_amount_sat: Option = row.get(28)?; + let maybe_send_swap_state: Option = row.get(29)?; + let maybe_send_swap_pair_fees_json: Option = row.get(30)?; let maybe_send_swap_pair_fees: Option = maybe_send_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok()); - let maybe_chain_swap_id: Option = row.get(29)?; - let maybe_chain_swap_created_at: Option = row.get(30)?; - let maybe_chain_swap_direction: Option = row.get(31)?; - let maybe_chain_swap_preimage: Option = row.get(32)?; - let maybe_chain_swap_description: Option = row.get(33)?; - let maybe_chain_swap_refund_tx_id: Option = row.get(34)?; - let maybe_chain_swap_payer_amount_sat: Option = row.get(35)?; - let maybe_chain_swap_receiver_amount_sat: Option = row.get(36)?; - let maybe_chain_swap_claim_address: Option = row.get(37)?; - let maybe_chain_swap_state: Option = row.get(38)?; - let maybe_chain_swap_pair_fees_json: Option = row.get(39)?; + let maybe_chain_swap_id: Option = row.get(31)?; + let maybe_chain_swap_created_at: Option = row.get(32)?; + let maybe_chain_swap_timeout_block_height: Option = row.get(33)?; + let maybe_chain_swap_direction: Option = row.get(34)?; + let maybe_chain_swap_preimage: Option = row.get(35)?; + let maybe_chain_swap_description: Option = row.get(36)?; + let maybe_chain_swap_refund_tx_id: Option = row.get(37)?; + let maybe_chain_swap_payer_amount_sat: Option = row.get(38)?; + let maybe_chain_swap_receiver_amount_sat: Option = row.get(39)?; + let maybe_chain_swap_claim_address: Option = row.get(40)?; + let maybe_chain_swap_state: Option = row.get(41)?; + let maybe_chain_swap_pair_fees_json: Option = row.get(42)?; let maybe_chain_swap_pair_fees: Option = maybe_chain_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok()); - let maybe_chain_swap_actual_payer_amount_sat: Option = row.get(40)?; - let maybe_chain_swap_accepted_receiver_amount_sat: Option = row.get(41)?; + let maybe_chain_swap_actual_payer_amount_sat: Option = row.get(43)?; + let maybe_chain_swap_accepted_receiver_amount_sat: Option = row.get(44)?; - let maybe_swap_refund_tx_amount_sat: Option = row.get(42)?; + let maybe_swap_refund_tx_amount_sat: Option = row.get(45)?; - let maybe_payment_details_destination: Option = row.get(43)?; - let maybe_payment_details_description: Option = row.get(44)?; - let maybe_payment_details_lnurl_info_json: Option = row.get(45)?; + let maybe_payment_details_destination: Option = row.get(46)?; + let maybe_payment_details_description: Option = row.get(47)?; + let maybe_payment_details_lnurl_info_json: Option = row.get(48)?; let maybe_payment_details_lnurl_info: Option = maybe_payment_details_lnurl_info_json.and_then(|info| serde_json::from_str(&info).ok()); @@ -519,6 +525,8 @@ impl Persister { swap_id: receive_swap_id, swap_type: PaymentSwapType::Receive, created_at: maybe_receive_swap_created_at.unwrap_or(utils::now()), + expiration_blockheight: maybe_receive_swap_timeout_block_height + .unwrap_or(0), preimage: maybe_receive_swap_preimage, bolt11: maybe_receive_swap_invoice.clone(), bolt12_offer: None, // Bolt12 not supported for Receive Swaps @@ -549,6 +557,8 @@ impl Persister { swap_id: send_swap_id, swap_type: PaymentSwapType::Send, created_at: maybe_send_swap_created_at.unwrap_or(utils::now()), + expiration_blockheight: maybe_send_swap_timeout_block_height + .unwrap_or(0), preimage: maybe_send_swap_preimage, bolt11: match maybe_send_swap_bolt12_offer.is_some() { true => None, // We don't expose the Bolt12 invoice @@ -598,6 +608,8 @@ impl Persister { swap_id: chain_swap_id, swap_type: PaymentSwapType::Chain, created_at: maybe_chain_swap_created_at.unwrap_or(utils::now()), + expiration_blockheight: maybe_chain_swap_timeout_block_height + .unwrap_or(0), preimage: maybe_chain_swap_preimage, bolt11: None, bolt12_offer: None, // Bolt12 not supported for Chain Swaps @@ -634,6 +646,7 @@ impl Persister { refund_tx_id, preimage, refund_tx_amount_sat, + expiration_blockheight, .. } | PaymentSwapData { @@ -645,6 +658,7 @@ impl Persister { preimage, refund_tx_id, refund_tx_amount_sat, + expiration_blockheight, .. }, ) => PaymentDetails::Lightning { @@ -657,19 +671,31 @@ impl Persister { refund_tx_id, refund_tx_amount_sat, description: description.unwrap_or("Lightning transfer".to_string()), + liquid_expiration_blockheight: expiration_blockheight, }, Some(PaymentSwapData { swap_type: PaymentSwapType::Chain, swap_id, refund_tx_id, refund_tx_amount_sat, + expiration_blockheight, .. - }) => PaymentDetails::Bitcoin { - swap_id, - refund_tx_id, - refund_tx_amount_sat, - description: description.unwrap_or("Bitcoin transfer".to_string()), - }, + }) => { + let (bitcoin_expiration_blockheight, liquid_expiration_blockheight) = + match maybe_chain_swap_direction { + Some(Direction::Incoming) => (Some(expiration_blockheight), None), + Some(Direction::Outgoing) | None => (None, Some(expiration_blockheight)), + }; + + PaymentDetails::Bitcoin { + swap_id, + refund_tx_id, + refund_tx_amount_sat, + description: description.unwrap_or("Bitcoin transfer".to_string()), + liquid_expiration_blockheight, + bitcoin_expiration_blockheight, + } + } _ => PaymentDetails::Liquid { destination: maybe_payment_details_destination .unwrap_or("Destination unknown".to_string()), diff --git a/lib/core/src/persist/receive.rs b/lib/core/src/persist/receive.rs index 53d1e16ad..039732678 100644 --- a/lib/core/src/persist/receive.rs +++ b/lib/core/src/persist/receive.rs @@ -26,6 +26,7 @@ impl Persister { create_response_json, claim_private_key, invoice, + timeout_block_height, payment_hash, payer_amount_sat, receiver_amount_sat, @@ -35,7 +36,7 @@ impl Persister { state, pair_fees_json ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING ", ( @@ -45,6 +46,7 @@ impl Persister { &receive_swap.create_response_json, &receive_swap.claim_private_key, &receive_swap.invoice, + &receive_swap.timeout_block_height, &receive_swap.payment_hash, &receive_swap.payer_amount_sat, &receive_swap.receiver_amount_sat, @@ -126,6 +128,7 @@ impl Persister { rs.claim_private_key, rs.invoice, rs.payment_hash, + rs.timeout_block_height, rs.description, rs.payer_amount_sat, rs.receiver_amount_sat, @@ -171,17 +174,18 @@ impl Persister { claim_private_key: row.get(3)?, invoice: row.get(4)?, payment_hash: row.get(5)?, - description: row.get(6)?, - payer_amount_sat: row.get(7)?, - receiver_amount_sat: row.get(8)?, - claim_fees_sat: row.get(9)?, - claim_tx_id: row.get(10)?, - lockup_tx_id: row.get(11)?, - mrh_address: row.get(12)?, - mrh_tx_id: row.get(13)?, - created_at: row.get(14)?, - state: row.get(15)?, - pair_fees_json: row.get(16)?, + timeout_block_height: row.get(6)?, + description: row.get(7)?, + payer_amount_sat: row.get(8)?, + receiver_amount_sat: row.get(9)?, + claim_fees_sat: row.get(10)?, + claim_tx_id: row.get(11)?, + lockup_tx_id: row.get(12)?, + mrh_address: row.get(13)?, + mrh_tx_id: row.get(14)?, + created_at: row.get(15)?, + state: row.get(16)?, + pair_fees_json: row.get(17)?, }) } diff --git a/lib/core/src/persist/send.rs b/lib/core/src/persist/send.rs index 17de6e91f..d9e0b68b5 100644 --- a/lib/core/src/persist/send.rs +++ b/lib/core/src/persist/send.rs @@ -25,6 +25,7 @@ impl Persister { invoice, bolt12_offer, payment_hash, + timeout_block_height, payer_amount_sat, receiver_amount_sat, create_response_json, @@ -33,7 +34,7 @@ impl Persister { state, pair_fees_json ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING ", ( @@ -42,6 +43,7 @@ impl Persister { &send_swap.invoice, &send_swap.bolt12_offer, &send_swap.payment_hash, + &send_swap.timeout_block_height, &send_swap.payer_amount_sat, &send_swap.receiver_amount_sat, &send_swap.create_response_json, @@ -138,6 +140,7 @@ impl Persister { invoice, bolt12_offer, payment_hash, + timeout_block_height, description, preimage, payer_amount_sat, @@ -178,17 +181,18 @@ impl Persister { invoice: row.get(1)?, bolt12_offer: row.get(2)?, payment_hash: row.get(3)?, - description: row.get(4)?, - preimage: row.get(5)?, - payer_amount_sat: row.get(6)?, - receiver_amount_sat: row.get(7)?, - create_response_json: row.get(8)?, - refund_private_key: row.get(9)?, - lockup_tx_id: row.get(10)?, - refund_tx_id: row.get(11)?, - created_at: row.get(12)?, - state: row.get(13)?, - pair_fees_json: row.get(14)?, + timeout_block_height: row.get(4)?, + description: row.get(5)?, + preimage: row.get(6)?, + payer_amount_sat: row.get(7)?, + receiver_amount_sat: row.get(8)?, + create_response_json: row.get(9)?, + refund_private_key: row.get(10)?, + lockup_tx_id: row.get(11)?, + refund_tx_id: row.get(12)?, + created_at: row.get(13)?, + state: row.get(14)?, + pair_fees_json: row.get(15)?, }) } diff --git a/lib/core/src/recover/recoverer.rs b/lib/core/src/recover/recoverer.rs index bde28e1c4..26be6a1b8 100644 --- a/lib/core/src/recover/recoverer.rs +++ b/lib/core/src/recover/recoverer.rs @@ -149,8 +149,8 @@ impl Recoverer { &swaps_list.receive_chain_swap_immutable_data_by_swap_id, )?; - let bitcoin_height = self.bitcoin_chain_service.lock().await.tip()?.height as u32; - let liquid_height = self.liquid_chain_service.lock().await.tip().await?; + let bitcoin_tip = self.bitcoin_chain_service.lock().await.tip()?; + let liquid_tip = self.liquid_chain_service.lock().await.tip().await?; for swap in swaps.iter_mut() { let swap_id = &swap.id(); @@ -160,8 +160,8 @@ impl Recoverer { log::warn!("Could not apply recovered data for Send swap {swap_id}: recovery data not found"); continue; }; - let is_expired = liquid_height - >= send_swap.get_boltz_create_response()?.timeout_block_height as u32; + let timeout_block_height = send_swap.timeout_block_height as u32; + let is_expired = liquid_tip >= timeout_block_height; if let Some(new_state) = recovered_data.derive_partial_state(is_expired) { send_swap.state = new_state; } @@ -182,10 +182,8 @@ impl Recoverer { log::warn!("Could not apply recovered data for Receive swap {swap_id}: recovery data not found"); continue; }; - let is_expired = liquid_height - >= receive_swap - .get_boltz_create_response()? - .timeout_block_height; + let timeout_block_height = receive_swap.timeout_block_height; + let is_expired = liquid_tip >= timeout_block_height; if let Some(new_state) = recovered_data.derive_partial_state(is_expired) { receive_swap.state = new_state; } @@ -214,7 +212,8 @@ impl Recoverer { }; chain_swap.actual_payer_amount_sat = Some(recovered_data.btc_user_lockup_amount_sat); - let is_expired = bitcoin_height >= chain_swap.timeout_block_height; + let is_expired = + bitcoin_tip.height as u32 >= chain_swap.timeout_block_height; let min_lockup_amount_sat = chain_swap.payer_amount_sat; if let Some(new_state) = recovered_data.derive_partial_state( min_lockup_amount_sat, @@ -248,7 +247,7 @@ impl Recoverer { log::warn!("Could not apply recovered data for outgoing Chain swap {swap_id}: recovery data not found"); continue; }; - let is_expired = liquid_height >= chain_swap.timeout_block_height; + let is_expired = liquid_tip >= chain_swap.timeout_block_height; if let Some(new_state) = recovered_data.derive_partial_state(is_expired) { chain_swap.state = new_state; } diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 283eae3c7..b3462224d 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -356,11 +356,11 @@ impl LiquidSdk { _ = interval.tick() => { // Get the Liquid tip and process a new block let liquid_tip_res = cloned.liquid_chain_service.lock().await.tip().await; - let is_new_liquid_block = match liquid_tip_res { + let is_new_liquid_block = match &liquid_tip_res { Ok(height) => { debug!("Got Liquid tip: {height}"); - let is_new_liquid_block = height > current_liquid_block; - current_liquid_block = height; + let is_new_liquid_block = *height > current_liquid_block; + current_liquid_block = *height; is_new_liquid_block }, Err(e) => { @@ -370,11 +370,11 @@ impl LiquidSdk { }; // Get the Bitcoin tip and process a new block let bitcoin_tip_res = cloned.bitcoin_chain_service.lock().await.tip().map(|tip| tip.height as u32); - let is_new_bitcoin_block = match bitcoin_tip_res { + let is_new_bitcoin_block = match &bitcoin_tip_res { Ok(height) => { debug!("Got Bitcoin tip: {height}"); - let is_new_bitcoin_block = height > current_bitcoin_block; - current_bitcoin_block = height; + let is_new_bitcoin_block = *height > current_bitcoin_block; + current_bitcoin_block = *height; is_new_bitcoin_block }, Err(e) => { @@ -383,6 +383,14 @@ impl LiquidSdk { } }; + if let (Ok(liquid_tip), Ok(bitcoin_tip)) = (liquid_tip_res, bitcoin_tip_res) { + cloned.persister.set_blockchain_info(&BlockchainInfo { + liquid_tip, + bitcoin_tip + }) + .unwrap_or_else(|err| warn!("Could not update local tips: {err:?}")); + }; + // Only partial sync when there are no new Liquid or Bitcoin blocks let partial_sync = (is_new_liquid_block || is_new_bitcoin_block).not(); _ = cloned.sync(partial_sync).await; @@ -610,15 +618,15 @@ impl LiquidSdk { Ok(()) } - /// Get the wallet info from persistant storage + /// Get the wallet and blockchain info from local storage pub async fn get_info(&self) -> SdkResult { self.ensure_is_started().await?; - let maybe_wallet_info = self.persister.get_wallet_info()?; - match maybe_wallet_info { - Some(wallet_info) => Ok(wallet_info), + let maybe_info = self.persister.get_info()?; + match maybe_info { + Some(info) => Ok(info), None => { self.update_wallet_info().await?; - self.persister.get_wallet_info()?.ok_or(SdkError::Generic { + self.persister.get_info()?.ok_or(SdkError::Generic { err: "Info not found".into(), }) } @@ -920,8 +928,8 @@ impl LiquidSdk { (receiver_amount_sat, fees_sat) = match amount { PayAmount::Drain => { ensure_sdk!( - get_info_res.pending_receive_sat == 0 - && get_info_res.pending_send_sat == 0, + get_info_res.wallet_info.pending_receive_sat == 0 + && get_info_res.wallet_info.pending_send_sat == 0, PaymentError::Generic { err: "Cannot drain while there are pending payments".to_string(), } @@ -929,7 +937,8 @@ impl LiquidSdk { let drain_fees_sat = self .estimate_drain_tx_fee(None, Some(&liquid_address_data.address)) .await?; - let drain_amount_sat = get_info_res.balance_sat - drain_fees_sat; + let drain_amount_sat = + get_info_res.wallet_info.balance_sat - drain_fees_sat; info!("Drain amount: {drain_amount_sat} sat"); (drain_amount_sat, drain_fees_sat) } @@ -1026,7 +1035,7 @@ impl LiquidSdk { let payer_amount_sat = receiver_amount_sat + fees_sat; ensure_sdk!( - payer_amount_sat <= get_info_res.balance_sat, + payer_amount_sat <= get_info_res.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1090,7 +1099,7 @@ impl LiquidSdk { let payer_amount_sat = amount_sat + fees_sat; ensure_sdk!( - payer_amount_sat <= self.get_info().await?.balance_sat, + payer_amount_sat <= self.get_info().await?.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1124,7 +1133,7 @@ impl LiquidSdk { let amount_sat = get_invoice_amount!(invoice); let payer_amount_sat = amount_sat + fees_sat; ensure_sdk!( - payer_amount_sat <= self.get_info().await?.balance_sat, + payer_amount_sat <= self.get_info().await?.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1181,7 +1190,7 @@ impl LiquidSdk { let receiver_amount_sat = invoice.amount_msats() / 1_000; let payer_amount_sat = receiver_amount_sat + fees_sat; ensure_sdk!( - payer_amount_sat <= self.get_info().await?.balance_sat, + payer_amount_sat <= self.get_info().await?.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1360,6 +1369,7 @@ impl LiquidSdk { invoice: invoice.to_string(), bolt12_offer, payment_hash: Some(payment_hash.to_string()), + timeout_block_height: create_response.timeout_block_height, description, preimage: None, payer_amount_sat, @@ -1498,12 +1508,13 @@ impl LiquidSdk { } PayAmount::Drain => { ensure_sdk!( - get_info_res.pending_receive_sat == 0 && get_info_res.pending_send_sat == 0, + get_info_res.wallet_info.pending_receive_sat == 0 + && get_info_res.wallet_info.pending_send_sat == 0, PaymentError::Generic { err: "Cannot drain while there are pending payments".to_string(), } ); - let payer_amount_sat = get_info_res.balance_sat; + let payer_amount_sat = get_info_res.wallet_info.balance_sat; let lockup_fees_sat = self.estimate_drain_tx_fee(None, None).await?; let user_lockup_amount_sat = payer_amount_sat - lockup_fees_sat; @@ -1525,7 +1536,7 @@ impl LiquidSdk { }; ensure_sdk!( - payer_amount_sat <= get_info_res.balance_sat, + payer_amount_sat <= get_info_res.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1557,7 +1568,7 @@ impl LiquidSdk { info!("Paying onchain, request = {req:?}"); let claim_address = self.validate_bitcoin_address(&req.address).await?; - let balance_sat = self.get_info().await?.balance_sat; + let balance_sat = self.get_info().await?.wallet_info.balance_sat; let receiver_amount_sat = req.prepare_response.receiver_amount_sat; let pair = self.get_chain_pair(Direction::Outgoing)?; let claim_fees_sat = req.prepare_response.claim_fees_sat; @@ -1985,6 +1996,7 @@ impl LiquidSdk { Bolt11InvoiceDescription::Direct(msg) => Some(msg.to_string()), Bolt11InvoiceDescription::Hash(_) => None, }; + self.persister .insert_or_update_receive_swap(&ReceiveSwap { id: swap_id.clone(), @@ -1993,6 +2005,7 @@ impl LiquidSdk { claim_private_key: keypair.display_secret().to_string(), invoice: invoice.to_string(), payment_hash: Some(preimage_hash), + timeout_block_height: create_response.timeout_block_height, description: invoice_description, payer_amount_sat, receiver_amount_sat, @@ -2534,7 +2547,7 @@ impl LiquidSdk { } } - let info_response = GetInfoResponse { + let info_response = WalletInfo { balance_sat: wallet_amount_sat as u64, pending_send_sat, pending_receive_sat, diff --git a/lib/core/src/sync/model/data.rs b/lib/core/src/sync/model/data.rs index e79e677db..d6f148c40 100644 --- a/lib/core/src/sync/model/data.rs +++ b/lib/core/src/sync/model/data.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use serde::{Deserialize, Serialize}; use crate::{ @@ -122,6 +123,8 @@ pub(crate) struct SendSyncData { pub(crate) refund_private_key: String, pub(crate) payer_amount_sat: u64, pub(crate) receiver_amount_sat: u64, + #[serde(default)] + pub(crate) timeout_block_height: u64, pub(crate) created_at: u32, pub(crate) preimage: Option, pub(crate) bolt12_offer: Option, @@ -164,6 +167,7 @@ impl From for SendSyncData { refund_private_key: value.refund_private_key, payer_amount_sat: value.payer_amount_sat, receiver_amount_sat: value.receiver_amount_sat, + timeout_block_height: value.timeout_block_height, created_at: value.created_at, preimage: value.preimage, description: value.description, @@ -185,6 +189,7 @@ impl From for SendSwap { pair_fees_json: val.pair_fees_json, create_response_json: val.create_response_json, created_at: val.created_at, + timeout_block_height: val.timeout_block_height, refund_private_key: val.refund_private_key, bolt12_offer: val.bolt12_offer, state: PaymentState::Created, @@ -206,6 +211,8 @@ pub(crate) struct ReceiveSyncData { pub(crate) payer_amount_sat: u64, pub(crate) receiver_amount_sat: u64, pub(crate) mrh_address: String, + #[serde(default)] + pub(crate) timeout_block_height: u32, pub(crate) created_at: u32, pub(crate) payment_hash: Option, pub(crate) description: Option, @@ -240,6 +247,7 @@ impl From for ReceiveSyncData { payer_amount_sat: value.payer_amount_sat, receiver_amount_sat: value.receiver_amount_sat, mrh_address: value.mrh_address, + timeout_block_height: value.timeout_block_height, created_at: value.created_at, description: value.description, } @@ -261,6 +269,7 @@ impl From for ReceiveSwap { receiver_amount_sat: val.receiver_amount_sat, claim_fees_sat: val.claim_fees_sat, mrh_address: val.mrh_address, + timeout_block_height: val.timeout_block_height, created_at: val.created_at, state: PaymentState::Created, claim_tx_id: None, @@ -282,7 +291,7 @@ impl PaymentDetailsSyncData { pub(crate) fn merge(&mut self, other: &Self, updated_fields: &[String]) { for field in updated_fields { match field.as_str() { - "destination" => self.destination = other.destination.clone(), + "destination" => self.destination.clone_from(&other.destination), "description" => clone_if_set(&mut self.description, &other.description), "lnurl_info" => clone_if_set(&mut self.lnurl_info, &other.lnurl_info), _ => continue, @@ -355,7 +364,7 @@ impl SyncData { base.merge(other, updated_fields) } (SyncData::Receive(ref mut _base), SyncData::Receive(_other)) => { - log::warn!("Attempting to merge for unnecessary type SyncData::Receive"); + bail!("Merge not supported for sync data of type Receive") } ( SyncData::LastDerivationIndex(our_index), diff --git a/lib/core/src/test_utils/persist.rs b/lib/core/src/test_utils/persist.rs index 8e632b4be..693a88a0b 100644 --- a/lib/core/src/test_utils/persist.rs +++ b/lib/core/src/test_utils/persist.rs @@ -41,6 +41,7 @@ pub(crate) fn new_send_swap(payment_state: Option) -> SendSwap { invoice: invoice.to_string(), bolt12_offer: None, payment_hash: Some(payment_hash.to_string()), + timeout_block_height: 1459611, description: Some("Send to BTC lightning".to_string()), preimage: None, payer_amount_sat: 1149, @@ -89,6 +90,7 @@ pub(crate) fn new_receive_swap(payment_state: Option) -> ReceiveSw ReceiveSwap { id: generate_random_string(4), preimage: "49ef4cb865d78519e5b3cf6aae6b409e1b471fe8ddbda744582e23665a2252cf".to_string(), + timeout_block_height: 1459611, description: Some("Send to L-BTC address".to_string()), create_response_json: r#"{ "swap_tree": { diff --git a/lib/core/src/test_utils/sync.rs b/lib/core/src/test_utils/sync.rs index 9a7e86267..ac6243356 100644 --- a/lib/core/src/test_utils/sync.rs +++ b/lib/core/src/test_utils/sync.rs @@ -120,6 +120,7 @@ pub(crate) fn new_receive_sync_data() -> ReceiveSyncData { create_response_json: "".to_string(), payer_amount_sat: 0, receiver_amount_sat: 0, + timeout_block_height: 1459611, created_at: 0, claim_fees_sat: 0, claim_private_key: "".to_string(), @@ -139,6 +140,7 @@ pub(crate) fn new_send_sync_data(preimage: Option) -> SendSyncData { refund_private_key: "".to_string(), payer_amount_sat: 0, receiver_amount_sat: 0, + timeout_block_height: 1459611, created_at: 0, preimage, payment_hash: None, diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 798674ed6..a9a638cb7 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -1344,6 +1344,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + BlockchainInfo dco_decode_blockchain_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return BlockchainInfo( + liquidTip: dco_decode_u_32(arr[0]), + bitcoinTip: dco_decode_u_32(arr[1]), + ); + } + @protected bool dco_decode_bool(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1778,13 +1789,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { GetInfoResponse dco_decode_get_info_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); return GetInfoResponse( - balanceSat: dco_decode_u_64(arr[0]), - pendingSendSat: dco_decode_u_64(arr[1]), - pendingReceiveSat: dco_decode_u_64(arr[2]), - fingerprint: dco_decode_String(arr[3]), - pubkey: dco_decode_String(arr[4]), + walletInfo: dco_decode_wallet_info(arr[0]), + blockchainInfo: dco_decode_blockchain_info(arr[1]), ); } @@ -2578,13 +2586,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return PaymentDetails_Lightning( swapId: dco_decode_String(raw[1]), description: dco_decode_String(raw[2]), - preimage: dco_decode_opt_String(raw[3]), - bolt11: dco_decode_opt_String(raw[4]), - bolt12Offer: dco_decode_opt_String(raw[5]), - paymentHash: dco_decode_opt_String(raw[6]), - lnurlInfo: dco_decode_opt_box_autoadd_ln_url_info(raw[7]), - refundTxId: dco_decode_opt_String(raw[8]), - refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[9]), + liquidExpirationBlockheight: dco_decode_u_32(raw[3]), + preimage: dco_decode_opt_String(raw[4]), + bolt11: dco_decode_opt_String(raw[5]), + bolt12Offer: dco_decode_opt_String(raw[6]), + paymentHash: dco_decode_opt_String(raw[7]), + lnurlInfo: dco_decode_opt_box_autoadd_ln_url_info(raw[8]), + refundTxId: dco_decode_opt_String(raw[9]), + refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[10]), ); case 1: return PaymentDetails_Liquid( @@ -2595,8 +2604,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return PaymentDetails_Bitcoin( swapId: dco_decode_String(raw[1]), description: dco_decode_String(raw[2]), - refundTxId: dco_decode_opt_String(raw[3]), - refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[4]), + liquidExpirationBlockheight: dco_decode_opt_box_autoadd_u_32(raw[3]), + bitcoinExpirationBlockheight: dco_decode_opt_box_autoadd_u_32(raw[4]), + refundTxId: dco_decode_opt_String(raw[5]), + refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[6]), ); default: throw Exception("unreachable"); @@ -3180,6 +3191,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dcoDecodeU64(raw); } + @protected + WalletInfo dco_decode_wallet_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + return WalletInfo( + balanceSat: dco_decode_u_64(arr[0]), + pendingSendSat: dco_decode_u_64(arr[1]), + pendingReceiveSat: dco_decode_u_64(arr[2]), + fingerprint: dco_decode_String(arr[3]), + pubkey: dco_decode_String(arr[4]), + ); + } + @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3314,6 +3339,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { message: var_message); } + @protected + BlockchainInfo sse_decode_blockchain_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_liquidTip = sse_decode_u_32(deserializer); + var var_bitcoinTip = sse_decode_u_32(deserializer); + return BlockchainInfo(liquidTip: var_liquidTip, bitcoinTip: var_bitcoinTip); + } + @protected bool sse_decode_bool(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3748,17 +3781,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected GetInfoResponse sse_decode_get_info_response(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - var var_balanceSat = sse_decode_u_64(deserializer); - var var_pendingSendSat = sse_decode_u_64(deserializer); - var var_pendingReceiveSat = sse_decode_u_64(deserializer); - var var_fingerprint = sse_decode_String(deserializer); - var var_pubkey = sse_decode_String(deserializer); - return GetInfoResponse( - balanceSat: var_balanceSat, - pendingSendSat: var_pendingSendSat, - pendingReceiveSat: var_pendingReceiveSat, - fingerprint: var_fingerprint, - pubkey: var_pubkey); + var var_walletInfo = sse_decode_wallet_info(deserializer); + var var_blockchainInfo = sse_decode_blockchain_info(deserializer); + return GetInfoResponse(walletInfo: var_walletInfo, blockchainInfo: var_blockchainInfo); } @protected @@ -4689,6 +4714,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 0: var var_swapId = sse_decode_String(deserializer); var var_description = sse_decode_String(deserializer); + var var_liquidExpirationBlockheight = sse_decode_u_32(deserializer); var var_preimage = sse_decode_opt_String(deserializer); var var_bolt11 = sse_decode_opt_String(deserializer); var var_bolt12Offer = sse_decode_opt_String(deserializer); @@ -4699,6 +4725,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return PaymentDetails_Lightning( swapId: var_swapId, description: var_description, + liquidExpirationBlockheight: var_liquidExpirationBlockheight, preimage: var_preimage, bolt11: var_bolt11, bolt12Offer: var_bolt12Offer, @@ -4713,11 +4740,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 2: var var_swapId = sse_decode_String(deserializer); var var_description = sse_decode_String(deserializer); + var var_liquidExpirationBlockheight = sse_decode_opt_box_autoadd_u_32(deserializer); + var var_bitcoinExpirationBlockheight = sse_decode_opt_box_autoadd_u_32(deserializer); var var_refundTxId = sse_decode_opt_String(deserializer); var var_refundTxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); return PaymentDetails_Bitcoin( swapId: var_swapId, description: var_description, + liquidExpirationBlockheight: var_liquidExpirationBlockheight, + bitcoinExpirationBlockheight: var_bitcoinExpirationBlockheight, refundTxId: var_refundTxId, refundTxAmountSat: var_refundTxAmountSat); default: @@ -5244,6 +5275,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getBigUint64(); } + @protected + WalletInfo sse_decode_wallet_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_balanceSat = sse_decode_u_64(deserializer); + var var_pendingSendSat = sse_decode_u_64(deserializer); + var var_pendingReceiveSat = sse_decode_u_64(deserializer); + var var_fingerprint = sse_decode_String(deserializer); + var var_pubkey = sse_decode_String(deserializer); + return WalletInfo( + balanceSat: var_balanceSat, + pendingSendSat: var_pendingSendSat, + pendingReceiveSat: var_pendingReceiveSat, + fingerprint: var_fingerprint, + pubkey: var_pubkey); + } + @protected int cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk( BindingLiquidSdk raw) { @@ -5472,6 +5519,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_opt_String(self.message, serializer); } + @protected + void sse_encode_blockchain_info(BlockchainInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_32(self.liquidTip, serializer); + sse_encode_u_32(self.bitcoinTip, serializer); + } + @protected void sse_encode_bool(bool self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5882,11 +5936,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected void sse_encode_get_info_response(GetInfoResponse self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_u_64(self.balanceSat, serializer); - sse_encode_u_64(self.pendingSendSat, serializer); - sse_encode_u_64(self.pendingReceiveSat, serializer); - sse_encode_String(self.fingerprint, serializer); - sse_encode_String(self.pubkey, serializer); + sse_encode_wallet_info(self.walletInfo, serializer); + sse_encode_blockchain_info(self.blockchainInfo, serializer); } @protected @@ -6652,6 +6703,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case PaymentDetails_Lightning( swapId: final swapId, description: final description, + liquidExpirationBlockheight: final liquidExpirationBlockheight, preimage: final preimage, bolt11: final bolt11, bolt12Offer: final bolt12Offer, @@ -6663,6 +6715,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_i_32(0, serializer); sse_encode_String(swapId, serializer); sse_encode_String(description, serializer); + sse_encode_u_32(liquidExpirationBlockheight, serializer); sse_encode_opt_String(preimage, serializer); sse_encode_opt_String(bolt11, serializer); sse_encode_opt_String(bolt12Offer, serializer); @@ -6677,12 +6730,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case PaymentDetails_Bitcoin( swapId: final swapId, description: final description, + liquidExpirationBlockheight: final liquidExpirationBlockheight, + bitcoinExpirationBlockheight: final bitcoinExpirationBlockheight, refundTxId: final refundTxId, refundTxAmountSat: final refundTxAmountSat ): sse_encode_i_32(2, serializer); sse_encode_String(swapId, serializer); sse_encode_String(description, serializer); + sse_encode_opt_box_autoadd_u_32(liquidExpirationBlockheight, serializer); + sse_encode_opt_box_autoadd_u_32(bitcoinExpirationBlockheight, serializer); sse_encode_opt_String(refundTxId, serializer); sse_encode_opt_box_autoadd_u_64(refundTxAmountSat, serializer); default: @@ -7125,6 +7182,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putBigUint64(self); } + + @protected + void sse_encode_wallet_info(WalletInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_64(self.balanceSat, serializer); + sse_encode_u_64(self.pendingSendSat, serializer); + sse_encode_u_64(self.pendingReceiveSat, serializer); + sse_encode_String(self.fingerprint, serializer); + sse_encode_String(self.pubkey, serializer); + } } @sealed diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index ddaf430a5..f7625487f 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -71,6 +71,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BitcoinAddressData dco_decode_bitcoin_address_data(dynamic raw); + @protected + BlockchainInfo dco_decode_blockchain_info(dynamic raw); + @protected bool dco_decode_bool(dynamic raw); @@ -599,6 +602,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt dco_decode_usize(dynamic raw); + @protected + WalletInfo dco_decode_wallet_info(dynamic raw); + @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); @@ -646,6 +652,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BitcoinAddressData sse_decode_bitcoin_address_data(SseDeserializer deserializer); + @protected + BlockchainInfo sse_decode_blockchain_info(SseDeserializer deserializer); + @protected bool sse_decode_bool(SseDeserializer deserializer); @@ -1176,6 +1185,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_usize(SseDeserializer deserializer); + @protected + WalletInfo sse_decode_wallet_info(SseDeserializer deserializer); + @protected ffi.Pointer cst_encode_AnyhowException(AnyhowException raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1973,6 +1985,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.message = cst_encode_opt_String(apiObj.message); } + @protected + void cst_api_fill_to_wire_blockchain_info(BlockchainInfo apiObj, wire_cst_blockchain_info wireObj) { + wireObj.liquid_tip = cst_encode_u_32(apiObj.liquidTip); + wireObj.bitcoin_tip = cst_encode_u_32(apiObj.bitcoinTip); + } + @protected void cst_api_fill_to_wire_box_autoadd_aes_success_action_data( AesSuccessActionData apiObj, ffi.Pointer wireObj) { @@ -2317,11 +2335,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_get_info_response(GetInfoResponse apiObj, wire_cst_get_info_response wireObj) { - wireObj.balance_sat = cst_encode_u_64(apiObj.balanceSat); - wireObj.pending_send_sat = cst_encode_u_64(apiObj.pendingSendSat); - wireObj.pending_receive_sat = cst_encode_u_64(apiObj.pendingReceiveSat); - wireObj.fingerprint = cst_encode_String(apiObj.fingerprint); - wireObj.pubkey = cst_encode_String(apiObj.pubkey); + cst_api_fill_to_wire_wallet_info(apiObj.walletInfo, wireObj.wallet_info); + cst_api_fill_to_wire_blockchain_info(apiObj.blockchainInfo, wireObj.blockchain_info); } @protected @@ -2840,6 +2855,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { if (apiObj is PaymentDetails_Lightning) { var pre_swap_id = cst_encode_String(apiObj.swapId); var pre_description = cst_encode_String(apiObj.description); + var pre_liquid_expiration_blockheight = cst_encode_u_32(apiObj.liquidExpirationBlockheight); var pre_preimage = cst_encode_opt_String(apiObj.preimage); var pre_bolt11 = cst_encode_opt_String(apiObj.bolt11); var pre_bolt12_offer = cst_encode_opt_String(apiObj.bolt12Offer); @@ -2850,6 +2866,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.tag = 0; wireObj.kind.Lightning.swap_id = pre_swap_id; wireObj.kind.Lightning.description = pre_description; + wireObj.kind.Lightning.liquid_expiration_blockheight = pre_liquid_expiration_blockheight; wireObj.kind.Lightning.preimage = pre_preimage; wireObj.kind.Lightning.bolt11 = pre_bolt11; wireObj.kind.Lightning.bolt12_offer = pre_bolt12_offer; @@ -2870,11 +2887,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { if (apiObj is PaymentDetails_Bitcoin) { var pre_swap_id = cst_encode_String(apiObj.swapId); var pre_description = cst_encode_String(apiObj.description); + var pre_liquid_expiration_blockheight = + cst_encode_opt_box_autoadd_u_32(apiObj.liquidExpirationBlockheight); + var pre_bitcoin_expiration_blockheight = + cst_encode_opt_box_autoadd_u_32(apiObj.bitcoinExpirationBlockheight); var pre_refund_tx_id = cst_encode_opt_String(apiObj.refundTxId); var pre_refund_tx_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.refundTxAmountSat); wireObj.tag = 2; wireObj.kind.Bitcoin.swap_id = pre_swap_id; wireObj.kind.Bitcoin.description = pre_description; + wireObj.kind.Bitcoin.liquid_expiration_blockheight = pre_liquid_expiration_blockheight; + wireObj.kind.Bitcoin.bitcoin_expiration_blockheight = pre_bitcoin_expiration_blockheight; wireObj.kind.Bitcoin.refund_tx_id = pre_refund_tx_id; wireObj.kind.Bitcoin.refund_tx_amount_sat = pre_refund_tx_amount_sat; return; @@ -3340,6 +3363,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.matches_callback_domain = cst_encode_bool(apiObj.matchesCallbackDomain); } + @protected + void cst_api_fill_to_wire_wallet_info(WalletInfo apiObj, wire_cst_wallet_info wireObj) { + wireObj.balance_sat = cst_encode_u_64(apiObj.balanceSat); + wireObj.pending_send_sat = cst_encode_u_64(apiObj.pendingSendSat); + wireObj.pending_receive_sat = cst_encode_u_64(apiObj.pendingReceiveSat); + wireObj.fingerprint = cst_encode_String(apiObj.fingerprint); + wireObj.pubkey = cst_encode_String(apiObj.pubkey); + } + @protected int cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk( BindingLiquidSdk raw); @@ -3437,6 +3469,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_bitcoin_address_data(BitcoinAddressData self, SseSerializer serializer); + @protected + void sse_encode_blockchain_info(BlockchainInfo self, SseSerializer serializer); + @protected void sse_encode_bool(bool self, SseSerializer serializer); @@ -3975,6 +4010,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_wallet_info(WalletInfo self, SseSerializer serializer); } // Section: wire_class @@ -6133,6 +6171,9 @@ final class wire_cst_PaymentDetails_Lightning extends ffi.Struct { external ffi.Pointer description; + @ffi.Uint32() + external int liquid_expiration_blockheight; + external ffi.Pointer preimage; external ffi.Pointer bolt11; @@ -6159,6 +6200,10 @@ final class wire_cst_PaymentDetails_Bitcoin extends ffi.Struct { external ffi.Pointer description; + external ffi.Pointer liquid_expiration_blockheight; + + external ffi.Pointer bitcoin_expiration_blockheight; + external ffi.Pointer refund_tx_id; external ffi.Pointer refund_tx_amount_sat; @@ -6451,12 +6496,20 @@ final class wire_cst_list_refundable_swap extends ffi.Struct { external int len; } +final class wire_cst_blockchain_info extends ffi.Struct { + @ffi.Uint32() + external int liquid_tip; + + @ffi.Uint32() + external int bitcoin_tip; +} + final class wire_cst_check_message_response extends ffi.Struct { @ffi.Bool() external bool is_valid; } -final class wire_cst_get_info_response extends ffi.Struct { +final class wire_cst_wallet_info extends ffi.Struct { @ffi.Uint64() external int balance_sat; @@ -6471,6 +6524,12 @@ final class wire_cst_get_info_response extends ffi.Struct { external ffi.Pointer pubkey; } +final class wire_cst_get_info_response extends ffi.Struct { + external wire_cst_wallet_info wallet_info; + + external wire_cst_blockchain_info blockchain_info; +} + final class wire_cst_InputType_BitcoinAddress extends ffi.Struct { external ffi.Pointer address; } diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index ac2698089..5fb9baae4 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -30,6 +30,27 @@ class BackupRequest { other is BackupRequest && runtimeType == other.runtimeType && backupPath == other.backupPath; } +class BlockchainInfo { + final int liquidTip; + final int bitcoinTip; + + const BlockchainInfo({ + required this.liquidTip, + required this.bitcoinTip, + }); + + @override + int get hashCode => liquidTip.hashCode ^ bitcoinTip.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BlockchainInfo && + runtimeType == other.runtimeType && + liquidTip == other.liquidTip && + bitcoinTip == other.bitcoinTip; +} + /// An argument of [PrepareBuyBitcoinRequest] when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin]. enum BuyBitcoinProvider { moonpay, @@ -241,47 +262,27 @@ class ConnectRequest { /// Returned when calling [crate::sdk::LiquidSdk::get_info]. class GetInfoResponse { - /// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`. - final BigInt balanceSat; - - /// Amount that is being used for ongoing Send swaps - final BigInt pendingSendSat; - - /// Incoming amount that is pending from ongoing Receive swaps - final BigInt pendingReceiveSat; - - /// The wallet's fingerprint. It is used to build the working directory in [Config::get_wallet_dir]. - final String fingerprint; + /// The wallet information, such as the balance, fingerprint and public key + final WalletInfo walletInfo; - /// The wallet's pubkey. Used to verify signed messages. - final String pubkey; + /// The latest synced blockchain information, such as the Liquid/Bitcoin tips + final BlockchainInfo blockchainInfo; const GetInfoResponse({ - required this.balanceSat, - required this.pendingSendSat, - required this.pendingReceiveSat, - required this.fingerprint, - required this.pubkey, + required this.walletInfo, + required this.blockchainInfo, }); @override - int get hashCode => - balanceSat.hashCode ^ - pendingSendSat.hashCode ^ - pendingReceiveSat.hashCode ^ - fingerprint.hashCode ^ - pubkey.hashCode; + int get hashCode => walletInfo.hashCode ^ blockchainInfo.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is GetInfoResponse && runtimeType == other.runtimeType && - balanceSat == other.balanceSat && - pendingSendSat == other.pendingSendSat && - pendingReceiveSat == other.pendingReceiveSat && - fingerprint == other.fingerprint && - pubkey == other.pubkey; + walletInfo == other.walletInfo && + blockchainInfo == other.blockchainInfo; } @freezed @@ -709,7 +710,10 @@ sealed class PaymentDetails with _$PaymentDetails { /// Represents the invoice description required String description, - /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). + /// The height of the block at which the swap will no longer be valid + required int liquidExpirationBlockheight, + + /// The preimage of the paid invoice (proof of payment). String? preimage, /// Represents the Bolt11 invoice associated with a payment @@ -747,6 +751,14 @@ sealed class PaymentDetails with _$PaymentDetails { /// Represents the invoice description required String description, + /// The height of the Liquid block at which the swap will no longer be valid + /// It should always be populated in case of an outgoing chain swap + int? liquidExpirationBlockheight, + + /// The height of the Bitcoin block at which the swap will no longer be valid + /// It should always be populated in case of an incoming chain swap + int? bitcoinExpirationBlockheight, + /// For a Send swap which was refunded, this is the refund tx id String? refundTxId, @@ -1500,3 +1512,47 @@ class SignMessageResponse { identical(this, other) || other is SignMessageResponse && runtimeType == other.runtimeType && signature == other.signature; } + +class WalletInfo { + /// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`. + final BigInt balanceSat; + + /// Amount that is being used for ongoing Send swaps + final BigInt pendingSendSat; + + /// Incoming amount that is pending from ongoing Receive swaps + final BigInt pendingReceiveSat; + + /// The wallet's fingerprint. It is used to build the working directory in [Config::get_wallet_dir]. + final String fingerprint; + + /// The wallet's pubkey. Used to verify signed messages. + final String pubkey; + + const WalletInfo({ + required this.balanceSat, + required this.pendingSendSat, + required this.pendingReceiveSat, + required this.fingerprint, + required this.pubkey, + }); + + @override + int get hashCode => + balanceSat.hashCode ^ + pendingSendSat.hashCode ^ + pendingReceiveSat.hashCode ^ + fingerprint.hashCode ^ + pubkey.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WalletInfo && + runtimeType == other.runtimeType && + balanceSat == other.balanceSat && + pendingSendSat == other.pendingSendSat && + pendingReceiveSat == other.pendingReceiveSat && + fingerprint == other.fingerprint && + pubkey == other.pubkey; +} diff --git a/packages/dart/lib/src/model.freezed.dart b/packages/dart/lib/src/model.freezed.dart index 8b3d20ab0..761961fb3 100644 --- a/packages/dart/lib/src/model.freezed.dart +++ b/packages/dart/lib/src/model.freezed.dart @@ -791,6 +791,7 @@ abstract class _$$PaymentDetails_LightningImplCopyWith<$Res> implements $Payment $Res call( {String swapId, String description, + int liquidExpirationBlockheight, String? preimage, String? bolt11, String? bolt12Offer, @@ -815,6 +816,7 @@ class __$$PaymentDetails_LightningImplCopyWithImpl<$Res> $Res call({ Object? swapId = null, Object? description = null, + Object? liquidExpirationBlockheight = null, Object? preimage = freezed, Object? bolt11 = freezed, Object? bolt12Offer = freezed, @@ -832,6 +834,10 @@ class __$$PaymentDetails_LightningImplCopyWithImpl<$Res> ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, + liquidExpirationBlockheight: null == liquidExpirationBlockheight + ? _value.liquidExpirationBlockheight + : liquidExpirationBlockheight // ignore: cast_nullable_to_non_nullable + as int, preimage: freezed == preimage ? _value.preimage : preimage // ignore: cast_nullable_to_non_nullable @@ -870,6 +876,7 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { const _$PaymentDetails_LightningImpl( {required this.swapId, required this.description, + required this.liquidExpirationBlockheight, this.preimage, this.bolt11, this.bolt12Offer, @@ -886,7 +893,11 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { @override final String description; - /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). + /// The height of the block at which the swap will no longer be valid + @override + final int liquidExpirationBlockheight; + + /// The preimage of the paid invoice (proof of payment). @override final String? preimage; @@ -916,7 +927,7 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { @override String toString() { - return 'PaymentDetails.lightning(swapId: $swapId, description: $description, preimage: $preimage, bolt11: $bolt11, bolt12Offer: $bolt12Offer, paymentHash: $paymentHash, lnurlInfo: $lnurlInfo, refundTxId: $refundTxId, refundTxAmountSat: $refundTxAmountSat)'; + return 'PaymentDetails.lightning(swapId: $swapId, description: $description, liquidExpirationBlockheight: $liquidExpirationBlockheight, preimage: $preimage, bolt11: $bolt11, bolt12Offer: $bolt12Offer, paymentHash: $paymentHash, lnurlInfo: $lnurlInfo, refundTxId: $refundTxId, refundTxAmountSat: $refundTxAmountSat)'; } @override @@ -926,6 +937,8 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { other is _$PaymentDetails_LightningImpl && (identical(other.swapId, swapId) || other.swapId == swapId) && (identical(other.description, description) || other.description == description) && + (identical(other.liquidExpirationBlockheight, liquidExpirationBlockheight) || + other.liquidExpirationBlockheight == liquidExpirationBlockheight) && (identical(other.preimage, preimage) || other.preimage == preimage) && (identical(other.bolt11, bolt11) || other.bolt11 == bolt11) && (identical(other.bolt12Offer, bolt12Offer) || other.bolt12Offer == bolt12Offer) && @@ -937,8 +950,8 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { } @override - int get hashCode => Object.hash(runtimeType, swapId, description, preimage, bolt11, bolt12Offer, - paymentHash, lnurlInfo, refundTxId, refundTxAmountSat); + int get hashCode => Object.hash(runtimeType, swapId, description, liquidExpirationBlockheight, preimage, + bolt11, bolt12Offer, paymentHash, lnurlInfo, refundTxId, refundTxAmountSat); /// Create a copy of PaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -953,6 +966,7 @@ abstract class PaymentDetails_Lightning extends PaymentDetails { const factory PaymentDetails_Lightning( {required final String swapId, required final String description, + required final int liquidExpirationBlockheight, final String? preimage, final String? bolt11, final String? bolt12Offer, @@ -968,7 +982,10 @@ abstract class PaymentDetails_Lightning extends PaymentDetails { @override String get description; - /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). + /// The height of the block at which the swap will no longer be valid + int get liquidExpirationBlockheight; + + /// The preimage of the paid invoice (proof of payment). String? get preimage; /// Represents the Bolt11 invoice associated with a payment @@ -1102,7 +1119,13 @@ abstract class _$$PaymentDetails_BitcoinImplCopyWith<$Res> implements $PaymentDe __$$PaymentDetails_BitcoinImplCopyWithImpl<$Res>; @override @useResult - $Res call({String swapId, String description, String? refundTxId, BigInt? refundTxAmountSat}); + $Res call( + {String swapId, + String description, + int? liquidExpirationBlockheight, + int? bitcoinExpirationBlockheight, + String? refundTxId, + BigInt? refundTxAmountSat}); } /// @nodoc @@ -1120,6 +1143,8 @@ class __$$PaymentDetails_BitcoinImplCopyWithImpl<$Res> $Res call({ Object? swapId = null, Object? description = null, + Object? liquidExpirationBlockheight = freezed, + Object? bitcoinExpirationBlockheight = freezed, Object? refundTxId = freezed, Object? refundTxAmountSat = freezed, }) { @@ -1132,6 +1157,14 @@ class __$$PaymentDetails_BitcoinImplCopyWithImpl<$Res> ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, + liquidExpirationBlockheight: freezed == liquidExpirationBlockheight + ? _value.liquidExpirationBlockheight + : liquidExpirationBlockheight // ignore: cast_nullable_to_non_nullable + as int?, + bitcoinExpirationBlockheight: freezed == bitcoinExpirationBlockheight + ? _value.bitcoinExpirationBlockheight + : bitcoinExpirationBlockheight // ignore: cast_nullable_to_non_nullable + as int?, refundTxId: freezed == refundTxId ? _value.refundTxId : refundTxId // ignore: cast_nullable_to_non_nullable @@ -1148,7 +1181,12 @@ class __$$PaymentDetails_BitcoinImplCopyWithImpl<$Res> class _$PaymentDetails_BitcoinImpl extends PaymentDetails_Bitcoin { const _$PaymentDetails_BitcoinImpl( - {required this.swapId, required this.description, this.refundTxId, this.refundTxAmountSat}) + {required this.swapId, + required this.description, + this.liquidExpirationBlockheight, + this.bitcoinExpirationBlockheight, + this.refundTxId, + this.refundTxAmountSat}) : super._(); @override @@ -1158,6 +1196,16 @@ class _$PaymentDetails_BitcoinImpl extends PaymentDetails_Bitcoin { @override final String description; + /// The height of the Liquid block at which the swap will no longer be valid + /// It should always be populated in case of an outgoing chain swap + @override + final int? liquidExpirationBlockheight; + + /// The height of the Bitcoin block at which the swap will no longer be valid + /// It should always be populated in case of an incoming chain swap + @override + final int? bitcoinExpirationBlockheight; + /// For a Send swap which was refunded, this is the refund tx id @override final String? refundTxId; @@ -1168,7 +1216,7 @@ class _$PaymentDetails_BitcoinImpl extends PaymentDetails_Bitcoin { @override String toString() { - return 'PaymentDetails.bitcoin(swapId: $swapId, description: $description, refundTxId: $refundTxId, refundTxAmountSat: $refundTxAmountSat)'; + return 'PaymentDetails.bitcoin(swapId: $swapId, description: $description, liquidExpirationBlockheight: $liquidExpirationBlockheight, bitcoinExpirationBlockheight: $bitcoinExpirationBlockheight, refundTxId: $refundTxId, refundTxAmountSat: $refundTxAmountSat)'; } @override @@ -1178,13 +1226,18 @@ class _$PaymentDetails_BitcoinImpl extends PaymentDetails_Bitcoin { other is _$PaymentDetails_BitcoinImpl && (identical(other.swapId, swapId) || other.swapId == swapId) && (identical(other.description, description) || other.description == description) && + (identical(other.liquidExpirationBlockheight, liquidExpirationBlockheight) || + other.liquidExpirationBlockheight == liquidExpirationBlockheight) && + (identical(other.bitcoinExpirationBlockheight, bitcoinExpirationBlockheight) || + other.bitcoinExpirationBlockheight == bitcoinExpirationBlockheight) && (identical(other.refundTxId, refundTxId) || other.refundTxId == refundTxId) && (identical(other.refundTxAmountSat, refundTxAmountSat) || other.refundTxAmountSat == refundTxAmountSat)); } @override - int get hashCode => Object.hash(runtimeType, swapId, description, refundTxId, refundTxAmountSat); + int get hashCode => Object.hash(runtimeType, swapId, description, liquidExpirationBlockheight, + bitcoinExpirationBlockheight, refundTxId, refundTxAmountSat); /// Create a copy of PaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -1199,6 +1252,8 @@ abstract class PaymentDetails_Bitcoin extends PaymentDetails { const factory PaymentDetails_Bitcoin( {required final String swapId, required final String description, + final int? liquidExpirationBlockheight, + final int? bitcoinExpirationBlockheight, final String? refundTxId, final BigInt? refundTxAmountSat}) = _$PaymentDetails_BitcoinImpl; const PaymentDetails_Bitcoin._() : super._(); @@ -1209,6 +1264,14 @@ abstract class PaymentDetails_Bitcoin extends PaymentDetails { @override String get description; + /// The height of the Liquid block at which the swap will no longer be valid + /// It should always be populated in case of an outgoing chain swap + int? get liquidExpirationBlockheight; + + /// The height of the Bitcoin block at which the swap will no longer be valid + /// It should always be populated in case of an incoming chain swap + int? get bitcoinExpirationBlockheight; + /// For a Send swap which was refunded, this is the refund tx id String? get refundTxId; diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index fd0cfb0f0..2898060ae 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -4609,6 +4609,9 @@ final class wire_cst_PaymentDetails_Lightning extends ffi.Struct { external ffi.Pointer description; + @ffi.Uint32() + external int liquid_expiration_blockheight; + external ffi.Pointer preimage; external ffi.Pointer bolt11; @@ -4635,6 +4638,10 @@ final class wire_cst_PaymentDetails_Bitcoin extends ffi.Struct { external ffi.Pointer description; + external ffi.Pointer liquid_expiration_blockheight; + + external ffi.Pointer bitcoin_expiration_blockheight; + external ffi.Pointer refund_tx_id; external ffi.Pointer refund_tx_amount_sat; @@ -4927,12 +4934,20 @@ final class wire_cst_list_refundable_swap extends ffi.Struct { external int len; } +final class wire_cst_blockchain_info extends ffi.Struct { + @ffi.Uint32() + external int liquid_tip; + + @ffi.Uint32() + external int bitcoin_tip; +} + final class wire_cst_check_message_response extends ffi.Struct { @ffi.Bool() external bool is_valid; } -final class wire_cst_get_info_response extends ffi.Struct { +final class wire_cst_wallet_info extends ffi.Struct { @ffi.Uint64() external int balance_sat; @@ -4947,6 +4962,12 @@ final class wire_cst_get_info_response extends ffi.Struct { external ffi.Pointer pubkey; } +final class wire_cst_get_info_response extends ffi.Struct { + external wire_cst_wallet_info wallet_info; + + external wire_cst_blockchain_info blockchain_info; +} + final class wire_cst_InputType_BitcoinAddress extends ffi.Struct { external ffi.Pointer address; } diff --git a/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt b/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt index 2b608ca7a..41cb3911a 100644 --- a/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt @@ -169,6 +169,39 @@ fun asBitcoinAddressDataList(arr: ReadableArray): List { return list } +fun asBlockchainInfo(blockchainInfo: ReadableMap): BlockchainInfo? { + if (!validateMandatoryFields( + blockchainInfo, + arrayOf( + "liquidTip", + "bitcoinTip", + ), + ) + ) { + return null + } + val liquidTip = blockchainInfo.getInt("liquidTip").toUInt() + val bitcoinTip = blockchainInfo.getInt("bitcoinTip").toUInt() + return BlockchainInfo(liquidTip, bitcoinTip) +} + +fun readableMapOf(blockchainInfo: BlockchainInfo): ReadableMap = + readableMapOf( + "liquidTip" to blockchainInfo.liquidTip, + "bitcoinTip" to blockchainInfo.bitcoinTip, + ) + +fun asBlockchainInfoList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asBlockchainInfo(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asBuyBitcoinRequest(buyBitcoinRequest: ReadableMap): BuyBitcoinRequest? { if (!validateMandatoryFields( buyBitcoinRequest, @@ -621,31 +654,22 @@ fun asGetInfoResponse(getInfoResponse: ReadableMap): GetInfoResponse? { if (!validateMandatoryFields( getInfoResponse, arrayOf( - "balanceSat", - "pendingSendSat", - "pendingReceiveSat", - "fingerprint", - "pubkey", + "walletInfo", + "blockchainInfo", ), ) ) { return null } - val balanceSat = getInfoResponse.getDouble("balanceSat").toULong() - val pendingSendSat = getInfoResponse.getDouble("pendingSendSat").toULong() - val pendingReceiveSat = getInfoResponse.getDouble("pendingReceiveSat").toULong() - val fingerprint = getInfoResponse.getString("fingerprint")!! - val pubkey = getInfoResponse.getString("pubkey")!! - return GetInfoResponse(balanceSat, pendingSendSat, pendingReceiveSat, fingerprint, pubkey) + val walletInfo = getInfoResponse.getMap("walletInfo")?.let { asWalletInfo(it) }!! + val blockchainInfo = getInfoResponse.getMap("blockchainInfo")?.let { asBlockchainInfo(it) }!! + return GetInfoResponse(walletInfo, blockchainInfo) } fun readableMapOf(getInfoResponse: GetInfoResponse): ReadableMap = readableMapOf( - "balanceSat" to getInfoResponse.balanceSat, - "pendingSendSat" to getInfoResponse.pendingSendSat, - "pendingReceiveSat" to getInfoResponse.pendingReceiveSat, - "fingerprint" to getInfoResponse.fingerprint, - "pubkey" to getInfoResponse.pubkey, + "walletInfo" to readableMapOf(getInfoResponse.walletInfo), + "blockchainInfo" to readableMapOf(getInfoResponse.blockchainInfo), ) fun asGetInfoResponseList(arr: ReadableArray): List { @@ -2697,6 +2721,48 @@ fun asUrlSuccessActionDataList(arr: ReadableArray): List { return list } +fun asWalletInfo(walletInfo: ReadableMap): WalletInfo? { + if (!validateMandatoryFields( + walletInfo, + arrayOf( + "balanceSat", + "pendingSendSat", + "pendingReceiveSat", + "fingerprint", + "pubkey", + ), + ) + ) { + return null + } + val balanceSat = walletInfo.getDouble("balanceSat").toULong() + val pendingSendSat = walletInfo.getDouble("pendingSendSat").toULong() + val pendingReceiveSat = walletInfo.getDouble("pendingReceiveSat").toULong() + val fingerprint = walletInfo.getString("fingerprint")!! + val pubkey = walletInfo.getString("pubkey")!! + return WalletInfo(balanceSat, pendingSendSat, pendingReceiveSat, fingerprint, pubkey) +} + +fun readableMapOf(walletInfo: WalletInfo): ReadableMap = + readableMapOf( + "balanceSat" to walletInfo.balanceSat, + "pendingSendSat" to walletInfo.pendingSendSat, + "pendingReceiveSat" to walletInfo.pendingReceiveSat, + "fingerprint" to walletInfo.fingerprint, + "pubkey" to walletInfo.pubkey, + ) + +fun asWalletInfoList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asWalletInfo(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asAesSuccessActionDataResult(aesSuccessActionDataResult: ReadableMap): AesSuccessActionDataResult? { val type = aesSuccessActionDataResult.getString("type") @@ -3172,6 +3238,7 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { if (type == "lightning") { val swapId = paymentDetails.getString("swapId")!! val description = paymentDetails.getString("description")!! + val liquidExpirationBlockheight = paymentDetails.getInt("liquidExpirationBlockheight").toUInt() val preimage = if (hasNonNullKey(paymentDetails, "preimage")) paymentDetails.getString("preimage") else null val bolt11 = if (hasNonNullKey(paymentDetails, "bolt11")) paymentDetails.getString("bolt11") else null val bolt12Offer = if (hasNonNullKey(paymentDetails, "bolt12Offer")) paymentDetails.getString("bolt12Offer") else null @@ -3200,6 +3267,7 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { return PaymentDetails.Lightning( swapId, description, + liquidExpirationBlockheight, preimage, bolt11, bolt12Offer, @@ -3217,6 +3285,26 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { if (type == "bitcoin") { val swapId = paymentDetails.getString("swapId")!! val description = paymentDetails.getString("description")!! + val bitcoinExpirationBlockheight = + if (hasNonNullKey( + paymentDetails, + "bitcoinExpirationBlockheight", + ) + ) { + paymentDetails.getInt("bitcoinExpirationBlockheight").toUInt() + } else { + null + } + val liquidExpirationBlockheight = + if (hasNonNullKey( + paymentDetails, + "liquidExpirationBlockheight", + ) + ) { + paymentDetails.getInt("liquidExpirationBlockheight").toUInt() + } else { + null + } val refundTxId = if (hasNonNullKey(paymentDetails, "refundTxId")) paymentDetails.getString("refundTxId") else null val refundTxAmountSat = if (hasNonNullKey( @@ -3228,7 +3316,14 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { } else { null } - return PaymentDetails.Bitcoin(swapId, description, refundTxId, refundTxAmountSat) + return PaymentDetails.Bitcoin( + swapId, + description, + bitcoinExpirationBlockheight, + liquidExpirationBlockheight, + refundTxId, + refundTxAmountSat, + ) } return null } @@ -3240,6 +3335,7 @@ fun readableMapOf(paymentDetails: PaymentDetails): ReadableMap? { pushToMap(map, "type", "lightning") pushToMap(map, "swapId", paymentDetails.swapId) pushToMap(map, "description", paymentDetails.description) + pushToMap(map, "liquidExpirationBlockheight", paymentDetails.liquidExpirationBlockheight) pushToMap(map, "preimage", paymentDetails.preimage) pushToMap(map, "bolt11", paymentDetails.bolt11) pushToMap(map, "bolt12Offer", paymentDetails.bolt12Offer) @@ -3257,6 +3353,8 @@ fun readableMapOf(paymentDetails: PaymentDetails): ReadableMap? { pushToMap(map, "type", "bitcoin") pushToMap(map, "swapId", paymentDetails.swapId) pushToMap(map, "description", paymentDetails.description) + pushToMap(map, "bitcoinExpirationBlockheight", paymentDetails.bitcoinExpirationBlockheight) + pushToMap(map, "liquidExpirationBlockheight", paymentDetails.liquidExpirationBlockheight) pushToMap(map, "refundTxId", paymentDetails.refundTxId) pushToMap(map, "refundTxAmountSat", paymentDetails.refundTxAmountSat) } diff --git a/packages/react-native/ios/BreezSDKLiquidMapper.swift b/packages/react-native/ios/BreezSDKLiquidMapper.swift index 2523dd35b..5c459a20a 100644 --- a/packages/react-native/ios/BreezSDKLiquidMapper.swift +++ b/packages/react-native/ios/BreezSDKLiquidMapper.swift @@ -204,6 +204,41 @@ enum BreezSDKLiquidMapper { return bitcoinAddressDataList.map { v -> [String: Any?] in return dictionaryOf(bitcoinAddressData: v) } } + static func asBlockchainInfo(blockchainInfo: [String: Any?]) throws -> BlockchainInfo { + guard let liquidTip = blockchainInfo["liquidTip"] as? UInt32 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "liquidTip", typeName: "BlockchainInfo")) + } + guard let bitcoinTip = blockchainInfo["bitcoinTip"] as? UInt32 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "bitcoinTip", typeName: "BlockchainInfo")) + } + + return BlockchainInfo(liquidTip: liquidTip, bitcoinTip: bitcoinTip) + } + + static func dictionaryOf(blockchainInfo: BlockchainInfo) -> [String: Any?] { + return [ + "liquidTip": blockchainInfo.liquidTip, + "bitcoinTip": blockchainInfo.bitcoinTip, + ] + } + + static func asBlockchainInfoList(arr: [Any]) throws -> [BlockchainInfo] { + var list = [BlockchainInfo]() + for value in arr { + if let val = value as? [String: Any?] { + var blockchainInfo = try asBlockchainInfo(blockchainInfo: val) + list.append(blockchainInfo) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "BlockchainInfo")) + } + } + return list + } + + static func arrayOf(blockchainInfoList: [BlockchainInfo]) -> [Any] { + return blockchainInfoList.map { v -> [String: Any?] in return dictionaryOf(blockchainInfo: v) } + } + static func asBuyBitcoinRequest(buyBitcoinRequest: [String: Any?]) throws -> BuyBitcoinRequest { guard let prepareResponseTmp = buyBitcoinRequest["prepareResponse"] as? [String: Any?] else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "prepareResponse", typeName: "BuyBitcoinRequest")) @@ -703,32 +738,23 @@ enum BreezSDKLiquidMapper { } static func asGetInfoResponse(getInfoResponse: [String: Any?]) throws -> GetInfoResponse { - guard let balanceSat = getInfoResponse["balanceSat"] as? UInt64 else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "balanceSat", typeName: "GetInfoResponse")) - } - guard let pendingSendSat = getInfoResponse["pendingSendSat"] as? UInt64 else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pendingSendSat", typeName: "GetInfoResponse")) - } - guard let pendingReceiveSat = getInfoResponse["pendingReceiveSat"] as? UInt64 else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pendingReceiveSat", typeName: "GetInfoResponse")) + guard let walletInfoTmp = getInfoResponse["walletInfo"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "walletInfo", typeName: "GetInfoResponse")) } - guard let fingerprint = getInfoResponse["fingerprint"] as? String else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "fingerprint", typeName: "GetInfoResponse")) - } - guard let pubkey = getInfoResponse["pubkey"] as? String else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pubkey", typeName: "GetInfoResponse")) + let walletInfo = try asWalletInfo(walletInfo: walletInfoTmp) + + guard let blockchainInfoTmp = getInfoResponse["blockchainInfo"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "blockchainInfo", typeName: "GetInfoResponse")) } + let blockchainInfo = try asBlockchainInfo(blockchainInfo: blockchainInfoTmp) - return GetInfoResponse(balanceSat: balanceSat, pendingSendSat: pendingSendSat, pendingReceiveSat: pendingReceiveSat, fingerprint: fingerprint, pubkey: pubkey) + return GetInfoResponse(walletInfo: walletInfo, blockchainInfo: blockchainInfo) } static func dictionaryOf(getInfoResponse: GetInfoResponse) -> [String: Any?] { return [ - "balanceSat": getInfoResponse.balanceSat, - "pendingSendSat": getInfoResponse.pendingSendSat, - "pendingReceiveSat": getInfoResponse.pendingReceiveSat, - "fingerprint": getInfoResponse.fingerprint, - "pubkey": getInfoResponse.pubkey, + "walletInfo": dictionaryOf(walletInfo: getInfoResponse.walletInfo), + "blockchainInfo": dictionaryOf(blockchainInfo: getInfoResponse.blockchainInfo), ] } @@ -3094,6 +3120,53 @@ enum BreezSDKLiquidMapper { return urlSuccessActionDataList.map { v -> [String: Any?] in return dictionaryOf(urlSuccessActionData: v) } } + static func asWalletInfo(walletInfo: [String: Any?]) throws -> WalletInfo { + guard let balanceSat = walletInfo["balanceSat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "balanceSat", typeName: "WalletInfo")) + } + guard let pendingSendSat = walletInfo["pendingSendSat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pendingSendSat", typeName: "WalletInfo")) + } + guard let pendingReceiveSat = walletInfo["pendingReceiveSat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pendingReceiveSat", typeName: "WalletInfo")) + } + guard let fingerprint = walletInfo["fingerprint"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "fingerprint", typeName: "WalletInfo")) + } + guard let pubkey = walletInfo["pubkey"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pubkey", typeName: "WalletInfo")) + } + + return WalletInfo(balanceSat: balanceSat, pendingSendSat: pendingSendSat, pendingReceiveSat: pendingReceiveSat, fingerprint: fingerprint, pubkey: pubkey) + } + + static func dictionaryOf(walletInfo: WalletInfo) -> [String: Any?] { + return [ + "balanceSat": walletInfo.balanceSat, + "pendingSendSat": walletInfo.pendingSendSat, + "pendingReceiveSat": walletInfo.pendingReceiveSat, + "fingerprint": walletInfo.fingerprint, + "pubkey": walletInfo.pubkey, + ] + } + + static func asWalletInfoList(arr: [Any]) throws -> [WalletInfo] { + var list = [WalletInfo]() + for value in arr { + if let val = value as? [String: Any?] { + var walletInfo = try asWalletInfo(walletInfo: val) + list.append(walletInfo) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "WalletInfo")) + } + } + return list + } + + static func arrayOf(walletInfoList: [WalletInfo]) -> [Any] { + return walletInfoList.map { v -> [String: Any?] in return dictionaryOf(walletInfo: v) } + } + static func asAesSuccessActionDataResult(aesSuccessActionDataResult: [String: Any?]) throws -> AesSuccessActionDataResult { let type = aesSuccessActionDataResult["type"] as! String if type == "decrypted" { @@ -3871,6 +3944,9 @@ enum BreezSDKLiquidMapper { guard let _description = paymentDetails["description"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "description", typeName: "PaymentDetails")) } + guard let _liquidExpirationBlockheight = paymentDetails["liquidExpirationBlockheight"] as? UInt32 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "liquidExpirationBlockheight", typeName: "PaymentDetails")) + } let _preimage = paymentDetails["preimage"] as? String let _bolt11 = paymentDetails["bolt11"] as? String @@ -3888,7 +3964,7 @@ enum BreezSDKLiquidMapper { let _refundTxAmountSat = paymentDetails["refundTxAmountSat"] as? UInt64 - return PaymentDetails.lightning(swapId: _swapId, description: _description, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, lnurlInfo: _lnurlInfo, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) + return PaymentDetails.lightning(swapId: _swapId, description: _description, liquidExpirationBlockheight: _liquidExpirationBlockheight, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, lnurlInfo: _lnurlInfo, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) } if type == "liquid" { guard let _destination = paymentDetails["destination"] as? String else { @@ -3906,11 +3982,15 @@ enum BreezSDKLiquidMapper { guard let _description = paymentDetails["description"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "description", typeName: "PaymentDetails")) } + let _bitcoinExpirationBlockheight = paymentDetails["bitcoinExpirationBlockheight"] as? UInt32 + + let _liquidExpirationBlockheight = paymentDetails["liquidExpirationBlockheight"] as? UInt32 + let _refundTxId = paymentDetails["refundTxId"] as? String let _refundTxAmountSat = paymentDetails["refundTxAmountSat"] as? UInt64 - return PaymentDetails.bitcoin(swapId: _swapId, description: _description, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) + return PaymentDetails.bitcoin(swapId: _swapId, description: _description, bitcoinExpirationBlockheight: _bitcoinExpirationBlockheight, liquidExpirationBlockheight: _liquidExpirationBlockheight, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) } throw SdkError.Generic(message: "Unexpected type \(type) for enum PaymentDetails") @@ -3919,12 +3999,13 @@ enum BreezSDKLiquidMapper { static func dictionaryOf(paymentDetails: PaymentDetails) -> [String: Any?] { switch paymentDetails { case let .lightning( - swapId, description, preimage, bolt11, bolt12Offer, paymentHash, lnurlInfo, refundTxId, refundTxAmountSat + swapId, description, liquidExpirationBlockheight, preimage, bolt11, bolt12Offer, paymentHash, lnurlInfo, refundTxId, refundTxAmountSat ): return [ "type": "lightning", "swapId": swapId, "description": description, + "liquidExpirationBlockheight": liquidExpirationBlockheight, "preimage": preimage == nil ? nil : preimage, "bolt11": bolt11 == nil ? nil : bolt11, "bolt12Offer": bolt12Offer == nil ? nil : bolt12Offer, @@ -3944,12 +4025,14 @@ enum BreezSDKLiquidMapper { ] case let .bitcoin( - swapId, description, refundTxId, refundTxAmountSat + swapId, description, bitcoinExpirationBlockheight, liquidExpirationBlockheight, refundTxId, refundTxAmountSat ): return [ "type": "bitcoin", "swapId": swapId, "description": description, + "bitcoinExpirationBlockheight": bitcoinExpirationBlockheight == nil ? nil : bitcoinExpirationBlockheight, + "liquidExpirationBlockheight": liquidExpirationBlockheight == nil ? nil : liquidExpirationBlockheight, "refundTxId": refundTxId == nil ? nil : refundTxId, "refundTxAmountSat": refundTxAmountSat == nil ? nil : refundTxAmountSat, ] diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index f17447e9f..e3c02c1f7 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -46,6 +46,11 @@ export interface BitcoinAddressData { message?: string } +export interface BlockchainInfo { + liquidTip: number + bitcoinTip: number +} + export interface BuyBitcoinRequest { prepareResponse: PrepareBuyBitcoinResponse redirectUrl?: string @@ -120,11 +125,8 @@ export interface FiatCurrency { } export interface GetInfoResponse { - balanceSat: number - pendingSendSat: number - pendingReceiveSat: number - fingerprint: string - pubkey: string + walletInfo: WalletInfo + blockchainInfo: BlockchainInfo } export interface LnInvoice { @@ -454,6 +456,14 @@ export interface UrlSuccessActionData { matchesCallbackDomain: boolean } +export interface WalletInfo { + balanceSat: number + pendingSendSat: number + pendingReceiveSat: number + fingerprint: string + pubkey: string +} + export enum AesSuccessActionDataResultVariant { DECRYPTED = "decrypted", ERROR_STATUS = "errorStatus" @@ -632,6 +642,7 @@ export type PaymentDetails = { type: PaymentDetailsVariant.LIGHTNING, swapId: string description: string + liquidExpirationBlockheight: number preimage?: string bolt11?: string bolt12Offer?: string @@ -647,6 +658,8 @@ export type PaymentDetails = { type: PaymentDetailsVariant.BITCOIN, swapId: string description: string + bitcoinExpirationBlockheight?: number + liquidExpirationBlockheight?: number refundTxId?: string refundTxAmountSat?: number }