diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 95eb9032e..d22b88143 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -38,6 +38,11 @@ pub(crate) enum Command { #[arg(short, long)] amount_sat: Option, + /// Optional id of the asset, in case of a direct Liquid address + /// or amount-less BIP21 + #[clap(long = "asset")] + asset_id: Option, + /// Whether or not this is a drain operation. If true, all available funds will be used. #[arg(short, long)] drain: Option, @@ -72,11 +77,16 @@ pub(crate) enum Command { #[arg(short = 'm', long = "method")] payment_method: Option, - /// Amount the payer will send, in satoshi + /// Amount the payer will send, in satoshi units. If receiving a non Liquid Bitcoin + /// asset, this is the units of the asset to receive. /// If not specified, it will generate a BIP21 URI/Liquid address with no amount #[arg(short, long)] payer_amount_sat: Option, + /// Optional id of the asset to receive when the 'payment_method' is "liquid" + #[clap(long = "asset")] + asset_id: Option, + /// Optional description for the invoice #[clap(short = 'd', long = "description")] description: Option, @@ -118,6 +128,10 @@ pub(crate) enum Command { #[clap(short = 'o', long = "offset")] offset: Option, + /// Optional id of the asset for Liquid payment method + #[clap(long = "asset")] + asset_id: Option, + /// Optional Liquid BIP21 URI/address for Liquid payment method #[clap(short = 'd', long = "destination")] destination: Option, @@ -267,12 +281,14 @@ pub(crate) async fn handle_command( Command::ReceivePayment { payment_method, payer_amount_sat, + asset_id, description, use_description_hash, } => { let prepare_response = sdk .prepare_receive_payment(&PrepareReceiveRequest { payer_amount_sat, + asset_id, payment_method: payment_method.unwrap_or(PaymentMethod::Lightning), }) .await?; @@ -333,6 +349,7 @@ pub(crate) async fn handle_command( offer, address, amount_sat, + asset_id, drain, delay, } => { @@ -364,6 +381,7 @@ pub(crate) async fn handle_command( .prepare_send_payment(&PrepareSendRequest { destination, amount, + asset_id, }) .await?; @@ -488,12 +506,18 @@ pub(crate) async fn handle_command( to_timestamp, limit, offset, + asset_id, destination, address, } => { - let details = match (destination, address) { - (Some(destination), None) => Some(ListPaymentDetails::Liquid { destination }), - (None, Some(address)) => Some(ListPaymentDetails::Bitcoin { address }), + let details = match (asset_id.clone(), destination.clone(), address) { + (None, Some(_), None) | (Some(_), None, None) | (Some(_), Some(_), None) => { + Some(ListPaymentDetails::Liquid { + asset_id, + destination, + }) + } + (None, None, Some(address)) => Some(ListPaymentDetails::Bitcoin { address }), _ => None, }; 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 e21bd885e..06ea9ac16 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 @@ -104,6 +104,7 @@ typedef struct wire_cst_list_payment_state { } wire_cst_list_payment_state; typedef struct wire_cst_ListPaymentDetails_Liquid { + struct wire_cst_list_prim_u_8_strict *asset_id; struct wire_cst_list_prim_u_8_strict *destination; } wire_cst_ListPaymentDetails_Liquid; @@ -373,8 +374,9 @@ typedef struct wire_cst_prepare_pay_onchain_request { } wire_cst_prepare_pay_onchain_request; typedef struct wire_cst_prepare_receive_request { - uint64_t *payer_amount_sat; int32_t payment_method; + uint64_t *payer_amount_sat; + struct wire_cst_list_prim_u_8_strict *asset_id; } wire_cst_prepare_receive_request; typedef struct wire_cst_prepare_refund_request { @@ -386,11 +388,13 @@ typedef struct wire_cst_prepare_refund_request { typedef struct wire_cst_prepare_send_request { struct wire_cst_list_prim_u_8_strict *destination; struct wire_cst_pay_amount *amount; + struct wire_cst_list_prim_u_8_strict *asset_id; } wire_cst_prepare_send_request; typedef struct wire_cst_prepare_receive_response { int32_t payment_method; uint64_t *payer_amount_sat; + struct wire_cst_list_prim_u_8_strict *asset_id; uint64_t fees_sat; uint64_t *min_payer_amount_sat; uint64_t *max_payer_amount_sat; @@ -501,6 +505,7 @@ typedef struct wire_cst_PaymentDetails_Lightning { typedef struct wire_cst_PaymentDetails_Liquid { struct wire_cst_list_prim_u_8_strict *destination; struct wire_cst_list_prim_u_8_strict *description; + struct wire_cst_list_prim_u_8_strict *asset_id; } wire_cst_PaymentDetails_Liquid; typedef struct wire_cst_PaymentDetails_Bitcoin { @@ -643,6 +648,16 @@ typedef struct wire_cst_symbol { uint32_t *position; } wire_cst_symbol; +typedef struct wire_cst_asset_balance { + struct wire_cst_list_prim_u_8_strict *asset_id; + uint64_t balance; +} wire_cst_asset_balance; + +typedef struct wire_cst_list_asset_balance { + struct wire_cst_asset_balance *ptr; + int32_t len; +} wire_cst_list_asset_balance; + typedef struct wire_cst_localized_name { struct wire_cst_list_prim_u_8_strict *locale; struct wire_cst_list_prim_u_8_strict *name; @@ -720,6 +735,7 @@ typedef struct wire_cst_get_info_response { uint64_t pending_receive_sat; struct wire_cst_list_prim_u_8_strict *fingerprint; struct wire_cst_list_prim_u_8_strict *pubkey; + struct wire_cst_list_asset_balance *asset_balances; } wire_cst_get_info_response; typedef struct wire_cst_InputType_BitcoinAddress { @@ -1349,6 +1365,8 @@ struct wire_cst_url_success_action_data *frbgen_breez_liquid_cst_new_box_autoadd struct wire_cst_list_String *frbgen_breez_liquid_cst_new_list_String(int32_t len); +struct wire_cst_list_asset_balance *frbgen_breez_liquid_cst_new_list_asset_balance(int32_t len); + struct wire_cst_list_external_input_parser *frbgen_breez_liquid_cst_new_list_external_input_parser(int32_t len); struct wire_cst_list_fiat_currency *frbgen_breez_liquid_cst_new_list_fiat_currency(int32_t len); @@ -1430,6 +1448,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_u_64); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_url_success_action_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_String); + dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_asset_balance); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_external_input_parser); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_fiat_currency); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_ln_offer_blinded_path); diff --git a/lib/bindings/src/breez_sdk_liquid.udl b/lib/bindings/src/breez_sdk_liquid.udl index 6a63085eb..4bd909537 100644 --- a/lib/bindings/src/breez_sdk_liquid.udl +++ b/lib/bindings/src/breez_sdk_liquid.udl @@ -355,12 +355,18 @@ dictionary ConnectWithSignerRequest { Config config; }; +dictionary AssetBalance { + string asset_id; + u64 balance; +}; + dictionary GetInfoResponse { u64 balance_sat; u64 pending_send_sat; u64 pending_receive_sat; string fingerprint; string pubkey; + sequence asset_balances; }; dictionary SignMessageRequest { @@ -403,6 +409,7 @@ dictionary LnUrlPayRequest { dictionary PrepareSendRequest { string destination; PayAmount? amount = null; + string? asset_id = null; }; [Enum] @@ -434,12 +441,14 @@ enum PaymentMethod { dictionary PrepareReceiveRequest { PaymentMethod payment_method; u64? payer_amount_sat = null; + string? asset_id = null; }; dictionary PrepareReceiveResponse { PaymentMethod payment_method; u64 fees_sat; u64? payer_amount_sat; + string? asset_id; u64? min_payer_amount_sat; u64? max_payer_amount_sat; f64? swapper_feerate; @@ -533,7 +542,7 @@ dictionary ListPaymentsRequest { [Enum] interface ListPaymentDetails { - Liquid(string destination); + Liquid(string? asset_id, string? destination); Bitcoin(string address); }; @@ -570,7 +579,7 @@ 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); - Liquid(string destination, string description); + Liquid(string asset_id, string destination, string description); Bitcoin(string swap_id, string description, string? refund_tx_id, u64? refund_tx_amount_sat); }; diff --git a/lib/core/src/chain_swap.rs b/lib/core/src/chain_swap.rs index d0b8a4829..0dee73b7c 100644 --- a/lib/core/src/chain_swap.rs +++ b/lib/core/src/chain_swap.rs @@ -16,7 +16,6 @@ use lwk_wollet::{ }; use tokio::sync::{broadcast, Mutex}; -use crate::model::{BlockListener, ChainSwapUpdate}; use crate::{ chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService}, ensure_sdk, @@ -31,6 +30,10 @@ use crate::{ utils, wallet::OnchainWallet, }; +use crate::{ + model::{BlockListener, ChainSwapUpdate}, + wallet::LIQUID_BTC_ASSET_ID, +}; // Estimates based on https://github.com/BoltzExchange/boltz-backend/blob/ee4c77be1fcb9bb2b45703c542ad67f7efbf218d/lib/rates/FeeProvider.ts#L68 pub const ESTIMATED_BTC_CLAIM_TX_VSIZE: u64 = 111; @@ -527,6 +530,7 @@ impl ChainSwapHandler { self.persister.insert_or_update_payment(PaymentTxData { tx_id: lockup_tx_id.clone(), timestamp: Some(utils::now()), + asset_id: LIQUID_BTC_ASSET_ID.to_string(), amount_sat: swap.receiver_amount_sat, // This should be: boltz fee + lockup fee + claim fee fees_sat: lockup_tx_fees_sat + swap.claim_fees_sat, @@ -737,6 +741,7 @@ impl ChainSwapHandler { .build_tx_or_drain_tx( self.config.lowball_fee_rate_msat_per_vbyte(), &lockup_details.lockup_address, + LIQUID_BTC_ASSET_ID.as_str(), lockup_details.amount, ) .await?; @@ -861,6 +866,7 @@ impl ChainSwapHandler { PaymentTxData { tx_id: claim_tx_id.clone(), timestamp: Some(utils::now()), + asset_id: LIQUID_BTC_ASSET_ID.to_string(), amount_sat: swap.receiver_amount_sat, fees_sat: 0, payment_type: PaymentType::Receive, diff --git a/lib/core/src/frb_generated.rs b/lib/core/src/frb_generated.rs index 2b74d2909..86c1eaf37 100644 --- a/lib/core/src/frb_generated.rs +++ b/lib/core/src/frb_generated.rs @@ -2379,6 +2379,18 @@ impl SseDecode for crate::bindings::Amount { } } +impl SseDecode for crate::model::AssetBalance { + // 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_assetId = ::sse_decode(deserializer); + let mut var_balance = ::sse_decode(deserializer); + return crate::model::AssetBalance { + asset_id: var_assetId, + balance: var_balance, + }; + } +} + impl SseDecode for crate::model::BackupRequest { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -2612,12 +2624,14 @@ impl SseDecode for crate::model::GetInfoResponse { let mut var_pendingReceiveSat = ::sse_decode(deserializer); let mut var_fingerprint = ::sse_decode(deserializer); let mut var_pubkey = ::sse_decode(deserializer); + let mut var_assetBalances = >::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, + asset_balances: var_assetBalances, }; } } @@ -2788,6 +2802,18 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -2870,8 +2896,10 @@ impl SseDecode for crate::model::ListPaymentDetails { let mut tag_ = ::sse_decode(deserializer); match tag_ { 0 => { - let mut var_destination = ::sse_decode(deserializer); + let mut var_assetId = >::sse_decode(deserializer); + let mut var_destination = >::sse_decode(deserializer); return crate::model::ListPaymentDetails::Liquid { + asset_id: var_assetId, destination: var_destination, }; } @@ -3783,9 +3811,11 @@ impl SseDecode for crate::model::PaymentDetails { 1 => { let mut var_destination = ::sse_decode(deserializer); let mut var_description = ::sse_decode(deserializer); + let mut var_assetId = ::sse_decode(deserializer); return crate::model::PaymentDetails::Liquid { destination: var_destination, description: var_description, + asset_id: var_assetId, }; } 2 => { @@ -4029,11 +4059,13 @@ impl SseDecode for crate::model::PreparePayOnchainResponse { impl SseDecode for crate::model::PrepareReceiveRequest { // 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_payerAmountSat = >::sse_decode(deserializer); let mut var_paymentMethod = ::sse_decode(deserializer); + let mut var_payerAmountSat = >::sse_decode(deserializer); + let mut var_assetId = >::sse_decode(deserializer); return crate::model::PrepareReceiveRequest { - payer_amount_sat: var_payerAmountSat, payment_method: var_paymentMethod, + payer_amount_sat: var_payerAmountSat, + asset_id: var_assetId, }; } } @@ -4043,6 +4075,7 @@ impl SseDecode for crate::model::PrepareReceiveResponse { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut var_paymentMethod = ::sse_decode(deserializer); let mut var_payerAmountSat = >::sse_decode(deserializer); + let mut var_assetId = >::sse_decode(deserializer); let mut var_feesSat = ::sse_decode(deserializer); let mut var_minPayerAmountSat = >::sse_decode(deserializer); let mut var_maxPayerAmountSat = >::sse_decode(deserializer); @@ -4050,6 +4083,7 @@ impl SseDecode for crate::model::PrepareReceiveResponse { return crate::model::PrepareReceiveResponse { payment_method: var_paymentMethod, payer_amount_sat: var_payerAmountSat, + asset_id: var_assetId, fees_sat: var_feesSat, min_payer_amount_sat: var_minPayerAmountSat, max_payer_amount_sat: var_maxPayerAmountSat, @@ -4091,9 +4125,11 @@ impl SseDecode for crate::model::PrepareSendRequest { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut var_destination = ::sse_decode(deserializer); let mut var_amount = >::sse_decode(deserializer); + let mut var_assetId = >::sse_decode(deserializer); return crate::model::PrepareSendRequest { destination: var_destination, amount: var_amount, + asset_id: var_assetId, }; } } @@ -4686,6 +4722,22 @@ impl flutter_rust_bridge::IntoIntoDart> } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::model::AssetBalance { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.asset_id.into_into_dart().into_dart(), + self.balance.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::AssetBalance {} +impl flutter_rust_bridge::IntoIntoDart for crate::model::AssetBalance { + fn into_into_dart(self) -> crate::model::AssetBalance { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::model::BackupRequest { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [self.backup_path.into_into_dart().into_dart()].into_dart() @@ -4990,6 +5042,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::GetInfoResponse { self.pending_receive_sat.into_into_dart().into_dart(), self.fingerprint.into_into_dart().into_dart(), self.pubkey.into_into_dart().into_dart(), + self.asset_balances.into_into_dart().into_dart(), ] .into_dart() } @@ -5162,9 +5215,15 @@ impl flutter_rust_bridge::IntoIntoDart impl flutter_rust_bridge::IntoDart for crate::model::ListPaymentDetails { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { - crate::model::ListPaymentDetails::Liquid { destination } => { - [0.into_dart(), destination.into_into_dart().into_dart()].into_dart() - } + crate::model::ListPaymentDetails::Liquid { + asset_id, + destination, + } => [ + 0.into_dart(), + asset_id.into_into_dart().into_dart(), + destination.into_into_dart().into_dart(), + ] + .into_dart(), crate::model::ListPaymentDetails::Bitcoin { address } => { [1.into_dart(), address.into_into_dart().into_dart()].into_dart() } @@ -5920,10 +5979,12 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentDetails { crate::model::PaymentDetails::Liquid { destination, description, + asset_id, } => [ 1.into_dart(), destination.into_into_dart().into_dart(), description.into_into_dart().into_dart(), + asset_id.into_into_dart().into_dart(), ] .into_dart(), crate::model::PaymentDetails::Bitcoin { @@ -6209,8 +6270,9 @@ impl flutter_rust_bridge::IntoIntoDart impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveRequest { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ - self.payer_amount_sat.into_into_dart().into_dart(), self.payment_method.into_into_dart().into_dart(), + self.payer_amount_sat.into_into_dart().into_dart(), + self.asset_id.into_into_dart().into_dart(), ] .into_dart() } @@ -6232,6 +6294,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveResponse { [ self.payment_method.into_into_dart().into_dart(), self.payer_amount_sat.into_into_dart().into_dart(), + self.asset_id.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(), self.min_payer_amount_sat.into_into_dart().into_dart(), self.max_payer_amount_sat.into_into_dart().into_dart(), @@ -6301,6 +6364,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareSendRequest { [ self.destination.into_into_dart().into_dart(), self.amount.into_into_dart().into_dart(), + self.asset_id.into_into_dart().into_dart(), ] .into_dart() } @@ -6915,6 +6979,14 @@ impl SseEncode for crate::bindings::Amount { } } +impl SseEncode for crate::model::AssetBalance { + // 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.asset_id, serializer); + ::sse_encode(self.balance, serializer); + } +} + impl SseEncode for crate::model::BackupRequest { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -7079,6 +7151,7 @@ impl SseEncode for crate::model::GetInfoResponse { ::sse_encode(self.pending_receive_sat, serializer); ::sse_encode(self.fingerprint, serializer); ::sse_encode(self.pubkey, serializer); + >::sse_encode(self.asset_balances, serializer); } } @@ -7217,6 +7290,16 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec { + // 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.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -7281,9 +7364,13 @@ impl SseEncode for crate::model::ListPaymentDetails { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { match self { - crate::model::ListPaymentDetails::Liquid { destination } => { + crate::model::ListPaymentDetails::Liquid { + asset_id, + destination, + } => { ::sse_encode(0, serializer); - ::sse_encode(destination, serializer); + >::sse_encode(asset_id, serializer); + >::sse_encode(destination, serializer); } crate::model::ListPaymentDetails::Bitcoin { address } => { ::sse_encode(1, serializer); @@ -8005,10 +8092,12 @@ impl SseEncode for crate::model::PaymentDetails { crate::model::PaymentDetails::Liquid { destination, description, + asset_id, } => { ::sse_encode(1, serializer); ::sse_encode(destination, serializer); ::sse_encode(description, serializer); + ::sse_encode(asset_id, serializer); } crate::model::PaymentDetails::Bitcoin { swap_id, @@ -8227,8 +8316,9 @@ impl SseEncode for crate::model::PreparePayOnchainResponse { impl SseEncode for crate::model::PrepareReceiveRequest { // 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.payer_amount_sat, serializer); ::sse_encode(self.payment_method, serializer); + >::sse_encode(self.payer_amount_sat, serializer); + >::sse_encode(self.asset_id, serializer); } } @@ -8237,6 +8327,7 @@ impl SseEncode for crate::model::PrepareReceiveResponse { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { ::sse_encode(self.payment_method, serializer); >::sse_encode(self.payer_amount_sat, serializer); + >::sse_encode(self.asset_id, serializer); ::sse_encode(self.fees_sat, serializer); >::sse_encode(self.min_payer_amount_sat, serializer); >::sse_encode(self.max_payer_amount_sat, serializer); @@ -8267,6 +8358,7 @@ impl SseEncode for crate::model::PrepareSendRequest { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { ::sse_encode(self.destination, serializer); >::sse_encode(self.amount, serializer); + >::sse_encode(self.asset_id, serializer); } } @@ -8757,6 +8849,15 @@ mod io { } } } + impl CstDecode for wire_cst_asset_balance { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::AssetBalance { + crate::model::AssetBalance { + asset_id: self.asset_id.cst_decode(), + balance: self.balance.cst_decode(), + } + } + } impl CstDecode for wire_cst_backup_request { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::BackupRequest { @@ -9296,6 +9397,7 @@ mod io { pending_receive_sat: self.pending_receive_sat.cst_decode(), fingerprint: self.fingerprint.cst_decode(), pubkey: self.pubkey.cst_decode(), + asset_balances: self.asset_balances.cst_decode(), } } } @@ -9425,6 +9527,16 @@ mod io { vec.into_iter().map(CstDecode::cst_decode).collect() } } + impl CstDecode> for *mut wire_cst_list_asset_balance { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> Vec { + let vec = unsafe { + let wrap = flutter_rust_bridge::for_generated::box_from_leak_ptr(self); + flutter_rust_bridge::for_generated::vec_from_leak_ptr(wrap.ptr, wrap.len) + }; + vec.into_iter().map(CstDecode::cst_decode).collect() + } + } impl CstDecode> for *mut wire_cst_list_external_input_parser { @@ -9496,6 +9608,7 @@ mod io { 0 => { let ans = unsafe { self.kind.Liquid }; crate::model::ListPaymentDetails::Liquid { + asset_id: ans.asset_id.cst_decode(), destination: ans.destination.cst_decode(), } } @@ -10072,6 +10185,7 @@ mod io { crate::model::PaymentDetails::Liquid { destination: ans.destination.cst_decode(), description: ans.description.cst_decode(), + asset_id: ans.asset_id.cst_decode(), } } 2 => { @@ -10232,8 +10346,9 @@ mod io { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::PrepareReceiveRequest { crate::model::PrepareReceiveRequest { - payer_amount_sat: self.payer_amount_sat.cst_decode(), payment_method: self.payment_method.cst_decode(), + payer_amount_sat: self.payer_amount_sat.cst_decode(), + asset_id: self.asset_id.cst_decode(), } } } @@ -10243,6 +10358,7 @@ mod io { crate::model::PrepareReceiveResponse { payment_method: self.payment_method.cst_decode(), payer_amount_sat: self.payer_amount_sat.cst_decode(), + asset_id: self.asset_id.cst_decode(), fees_sat: self.fees_sat.cst_decode(), min_payer_amount_sat: self.min_payer_amount_sat.cst_decode(), max_payer_amount_sat: self.max_payer_amount_sat.cst_decode(), @@ -10276,6 +10392,7 @@ mod io { crate::model::PrepareSendRequest { destination: self.destination.cst_decode(), amount: self.amount.cst_decode(), + asset_id: self.asset_id.cst_decode(), } } } @@ -10655,6 +10772,19 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_asset_balance { + fn new_with_null_ptr() -> Self { + Self { + asset_id: core::ptr::null_mut(), + balance: Default::default(), + } + } + } + impl Default for wire_cst_asset_balance { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_backup_request { fn new_with_null_ptr() -> Self { Self { @@ -10852,6 +10982,7 @@ mod io { pending_receive_sat: Default::default(), fingerprint: core::ptr::null_mut(), pubkey: core::ptr::null_mut(), + asset_balances: core::ptr::null_mut(), } } } @@ -11464,8 +11595,9 @@ mod io { impl NewWithNullPtr for wire_cst_prepare_receive_request { fn new_with_null_ptr() -> Self { Self { - payer_amount_sat: core::ptr::null_mut(), payment_method: Default::default(), + payer_amount_sat: core::ptr::null_mut(), + asset_id: core::ptr::null_mut(), } } } @@ -11479,6 +11611,7 @@ mod io { Self { payment_method: Default::default(), payer_amount_sat: core::ptr::null_mut(), + asset_id: core::ptr::null_mut(), fees_sat: Default::default(), min_payer_amount_sat: core::ptr::null_mut(), max_payer_amount_sat: core::ptr::null_mut(), @@ -11524,6 +11657,7 @@ mod io { Self { destination: core::ptr::null_mut(), amount: core::ptr::null_mut(), + asset_id: core::ptr::null_mut(), } } } @@ -12608,6 +12742,20 @@ mod io { flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) } + #[no_mangle] + pub extern "C" fn frbgen_breez_liquid_cst_new_list_asset_balance( + len: i32, + ) -> *mut wire_cst_list_asset_balance { + let wrap = wire_cst_list_asset_balance { + ptr: flutter_rust_bridge::for_generated::new_leak_vec_ptr( + ::new_with_null_ptr(), + len, + ), + len, + }; + flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) + } + #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_list_external_input_parser( len: i32, @@ -12846,6 +12994,12 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_asset_balance { + asset_id: *mut wire_cst_list_prim_u_8_strict, + balance: u64, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_backup_request { backup_path: *mut wire_cst_list_prim_u_8_strict, } @@ -12950,6 +13104,7 @@ mod io { pending_receive_sat: u64, fingerprint: *mut wire_cst_list_prim_u_8_strict, pubkey: *mut wire_cst_list_prim_u_8_strict, + asset_balances: *mut wire_cst_list_asset_balance, } #[repr(C)] #[derive(Clone, Copy)] @@ -13070,6 +13225,12 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_list_asset_balance { + ptr: *mut wire_cst_asset_balance, + len: i32, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_list_external_input_parser { ptr: *mut wire_cst_external_input_parser, len: i32, @@ -13120,6 +13281,7 @@ mod io { #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_ListPaymentDetails_Liquid { + asset_id: *mut wire_cst_list_prim_u_8_strict, destination: *mut wire_cst_list_prim_u_8_strict, } #[repr(C)] @@ -13615,6 +13777,7 @@ mod io { pub struct wire_cst_PaymentDetails_Liquid { destination: *mut wire_cst_list_prim_u_8_strict, description: *mut wire_cst_list_prim_u_8_strict, + asset_id: *mut wire_cst_list_prim_u_8_strict, } #[repr(C)] #[derive(Clone, Copy)] @@ -13742,14 +13905,16 @@ mod io { #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_prepare_receive_request { - payer_amount_sat: *mut u64, payment_method: i32, + payer_amount_sat: *mut u64, + asset_id: *mut wire_cst_list_prim_u_8_strict, } #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_prepare_receive_response { payment_method: i32, payer_amount_sat: *mut u64, + asset_id: *mut wire_cst_list_prim_u_8_strict, fees_sat: u64, min_payer_amount_sat: *mut u64, max_payer_amount_sat: *mut u64, @@ -13774,6 +13939,7 @@ mod io { pub struct wire_cst_prepare_send_request { destination: *mut wire_cst_list_prim_u_8_strict, amount: *mut wire_cst_pay_amount, + asset_id: *mut wire_cst_list_prim_u_8_strict, } #[repr(C)] #[derive(Clone, Copy)] diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index dd0f5083b..ceffbee73 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -27,6 +27,7 @@ use crate::receive_swap::{ DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET, }; use crate::utils; +use crate::wallet::LIQUID_BTC_ASSET_ID; // Both use f64 for the maximum precision when converting between units pub const STANDARD_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1; @@ -347,8 +348,16 @@ pub enum PaymentMethod { /// An argument when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. #[derive(Debug, Serialize)] pub struct PrepareReceiveRequest { - pub payer_amount_sat: Option, pub payment_method: PaymentMethod, + + /// The payer amount in satoshi units. If receiving a non Liquid Bitcoin asset, this is the units + /// of the asset to receive. + pub payer_amount_sat: Option, + + /// The id of the asset to receive when the 'payment_method' is Liquid. + /// + /// Defaults to Liquid Bitcoin. + pub asset_id: Option, } /// Returned when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. @@ -356,6 +365,7 @@ pub struct PrepareReceiveRequest { pub struct PrepareReceiveResponse { pub payment_method: PaymentMethod, pub payer_amount_sat: Option, + pub asset_id: Option, /// Generally represents the total fees that would be paid to send or receive this payment. /// @@ -437,6 +447,10 @@ pub struct PrepareSendRequest { /// Should only be set when paying directly onchain or to a BIP21 URI /// where no amount is specified, or when the caller wishes to drain pub amount: Option, + + /// Should only be set when paying directly onchain or to a BIP21 URI + /// where no asset id is specified + pub asset_id: Option, } /// Specifies the supported destinations which can be payed by the SDK @@ -540,6 +554,13 @@ pub struct RefundResponse { pub refund_tx_id: String, } +/// An asset balance to denote the balance for each asset. +#[derive(Debug, Serialize, Deserialize)] +pub struct AssetBalance { + pub asset_id: String, + pub balance: u64, +} + /// Returned when calling [crate::sdk::LiquidSdk::get_info]. #[derive(Debug, Serialize, Deserialize)] pub struct GetInfoResponse { @@ -553,6 +574,37 @@ pub struct GetInfoResponse { pub fingerprint: String, /// The wallet's pubkey. Used to verify signed messages. pub pubkey: String, + /// Asset balances of non Liquid Bitcoin assets + pub asset_balances: Vec, +} + +impl GetInfoResponse { + pub(crate) fn validate_sufficient_funds( + &self, + amount_sat: u64, + fees_sat: u64, + asset_id: &str, + ) -> Result<(), PaymentError> { + if asset_id.eq(LIQUID_BTC_ASSET_ID.as_str()) { + ensure_sdk!( + amount_sat + fees_sat <= self.balance_sat, + PaymentError::InsufficientFunds + ); + } else { + match self + .asset_balances + .iter() + .find(|ab| ab.asset_id.eq(asset_id)) + { + Some(asset_balance) => ensure_sdk!( + amount_sat <= asset_balance.balance && fees_sat <= self.balance_sat, + PaymentError::InsufficientFunds + ), + None => return Err(PaymentError::InsufficientFunds), + } + } + Ok(()) + } } /// An argument when calling [crate::sdk::LiquidSdk::sign_message]. @@ -620,7 +672,10 @@ pub struct ListPaymentsRequest { #[derive(Debug, Serialize)] pub enum ListPaymentDetails { /// The Liquid BIP21 URI or address of the payment - Liquid { destination: String }, + Liquid { + asset_id: Option, + destination: Option, + }, /// The Bitcoin address of the payment Bitcoin { address: String }, @@ -1260,6 +1315,9 @@ pub struct PaymentTxData { /// The point in time when the underlying tx was included in a block. pub timestamp: Option, + /// The asset id + pub asset_id: String, + /// The onchain tx amount. /// /// In case of an outbound payment (Send), this is the payer amount. Otherwise it's the receiver amount. @@ -1373,6 +1431,9 @@ pub enum PaymentDetails { /// Represents the BIP21 `message` field description: String, + + /// The asset id + asset_id: String, }, /// Swapping to or from the Bitcoin chain Bitcoin { @@ -1412,6 +1473,13 @@ impl PaymentDetails { Self::Liquid { .. } => None, } } + + pub(crate) fn is_liquid_btc_asset_id(&self) -> bool { + match self { + Self::Liquid { asset_id, .. } => asset_id.eq(LIQUID_BTC_ASSET_ID.as_str()), + _ => true, + } + } } /// Represents an SDK payment. diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index 87a772d29..d919f6dde 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -217,5 +217,6 @@ 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 payment_tx_data ADD COLUMN asset_id TEXT NOT NULL DEFAULT '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';", ] } diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 15cee000a..b43f2ba6e 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -15,9 +15,11 @@ use std::{fs::create_dir_all, path::PathBuf, str::FromStr}; use crate::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription}; use crate::model::*; use crate::sync::model::RecordType; +use crate::wallet::LIQUID_BTC_ASSET_ID; use crate::{get_invoice_description, utils}; use anyhow::{anyhow, Result}; use boltz_client::boltz::{ChainPair, ReversePair, SubmarinePair}; +use lwk_wollet::elements::AssetId; use lwk_wollet::WalletTx; use migrations::current_migrations; use model::PaymentTxDetails; @@ -104,20 +106,21 @@ impl Persister { pub(crate) fn insert_or_update_payment_with_wallet_tx(&self, tx: &WalletTx) -> Result<()> { let tx_id = tx.txid.to_string(); let is_tx_confirmed = tx.height.is_some(); - let amount_sat = tx - .balance - .iter() - .filter_map(|(asset_id, balance)| { - if *asset_id == lwk_wollet::elements::AssetId::LIQUID_BTC { - return Some(balance); - } - None - }) - .sum::(); - if amount_sat == 0 { - log::warn!("Attempted to persist a payment with no output amount: tx_id {tx_id}"); - return Ok(()); + let mut tx_balances = tx.balance.clone(); + // Remove the Liquid Bitcoin asset balance + let mut balance = tx_balances + .remove(&AssetId::LIQUID_BTC) + .map(|balance| (LIQUID_BTC_ASSET_ID.to_string(), balance)); + // If the balances are still not empty pop the asset balance + if tx_balances.is_empty().not() { + balance = tx_balances + .pop_first() + .map(|(asset_id, balance)| (asset_id.to_hex(), balance)); } + let Some((asset_id, amount_sat)) = balance else { + log::warn!("Attempted to persist a payment with no balance: tx_id {tx_id}"); + return Ok(()); + }; let maybe_script_pubkey = tx .outputs .iter() @@ -130,6 +133,7 @@ impl Persister { PaymentTxData { tx_id: tx_id.clone(), timestamp: tx.timestamp, + asset_id, amount_sat: amount_sat.unsigned_abs(), fees_sat: tx.fee, payment_type: match amount_sat >= 0 { @@ -153,6 +157,7 @@ impl Persister { let mut stmt = con.prepare( "SELECT tx_id, timestamp, + asset_id, amount_sat, fees_sat, payment_type, @@ -166,11 +171,12 @@ impl Persister { Ok(PaymentTxData { tx_id: row.get(0)?, timestamp: row.get(1)?, - amount_sat: row.get(2)?, - fees_sat: row.get(3)?, - payment_type: row.get(4)?, - is_confirmed: row.get(5)?, - unblinding_data: row.get(6)?, + asset_id: row.get(2)?, + amount_sat: row.get(3)?, + fees_sat: row.get(4)?, + payment_type: row.get(5)?, + is_confirmed: row.get(6)?, + unblinding_data: row.get(7)?, }) })? .map(|i| i.unwrap()) @@ -190,15 +196,17 @@ impl Persister { "INSERT INTO payment_tx_data ( tx_id, timestamp, + asset_id, amount_sat, fees_sat, payment_type, is_confirmed, unblinding_data ) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (tx_id) DO UPDATE SET timestamp = CASE WHEN excluded.is_confirmed = 1 THEN excluded.timestamp ELSE timestamp END, + asset_id = excluded.asset_id, amount_sat = excluded.amount_sat, fees_sat = excluded.fees_sat, payment_type = excluded.payment_type, @@ -208,6 +216,7 @@ impl Persister { ( &ptx.tx_id, ptx.timestamp.or(Some(utils::now())), + ptx.asset_id, ptx.amount_sat, ptx.fees_sat, ptx.payment_type, @@ -362,6 +371,7 @@ impl Persister { SELECT ptx.tx_id, ptx.timestamp, + ptx.asset_id, ptx.amount_sat, ptx.fees_sat, ptx.payment_type, @@ -449,64 +459,65 @@ impl Persister { Ok(ref tx_id) => Some(PaymentTxData { tx_id: tx_id.to_string(), timestamp: row.get(1)?, - amount_sat: row.get(2)?, - fees_sat: row.get(3)?, - payment_type: row.get(4)?, - is_confirmed: row.get(5)?, - unblinding_data: row.get(6)?, + asset_id: row.get(2)?, + amount_sat: row.get(3)?, + fees_sat: row.get(4)?, + payment_type: row.get(5)?, + is_confirmed: row.get(6)?, + unblinding_data: row.get(7)?, }), _ => None, }; - 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_id: Option = row.get(8)?; + let maybe_receive_swap_created_at: 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_invoice: Option = row.get(20)?; + let maybe_send_swap_bolt12_offer: Option = row.get(21)?; + let maybe_send_swap_payment_hash: Option = row.get(22)?; + let maybe_send_swap_description: Option = row.get(23)?; + let maybe_send_swap_preimage: Option = row.get(24)?; + let maybe_send_swap_refund_tx_id: Option = row.get(25)?; + let maybe_send_swap_payer_amount_sat: Option = row.get(26)?; + let maybe_send_swap_receiver_amount_sat: Option = row.get(27)?; + let maybe_send_swap_state: Option = row.get(28)?; + let maybe_send_swap_pair_fees_json: Option = row.get(29)?; 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(30)?; + let maybe_chain_swap_created_at: Option = row.get(31)?; + let maybe_chain_swap_direction: Option = row.get(32)?; + let maybe_chain_swap_preimage: Option = row.get(33)?; + let maybe_chain_swap_description: Option = row.get(34)?; + let maybe_chain_swap_refund_tx_id: Option = row.get(35)?; + let maybe_chain_swap_payer_amount_sat: Option = row.get(36)?; + let maybe_chain_swap_receiver_amount_sat: Option = row.get(37)?; + let maybe_chain_swap_claim_address: Option = row.get(38)?; + let maybe_chain_swap_state: Option = row.get(39)?; + let maybe_chain_swap_pair_fees_json: Option = row.get(40)?; 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(41)?; + let maybe_chain_swap_accepted_receiver_amount_sat: Option = row.get(42)?; - let maybe_swap_refund_tx_amount_sat: Option = row.get(42)?; + let maybe_swap_refund_tx_amount_sat: Option = row.get(43)?; - 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(44)?; + let maybe_payment_details_description: Option = row.get(45)?; + let maybe_payment_details_lnurl_info_json: Option = row.get(46)?; let maybe_payment_details_lnurl_info: Option = maybe_payment_details_lnurl_info_json.and_then(|info| serde_json::from_str(&info).ok()); @@ -671,6 +682,9 @@ impl Persister { description: description.unwrap_or("Bitcoin transfer".to_string()), }, _ => PaymentDetails::Liquid { + asset_id: tx + .clone() + .map_or(LIQUID_BTC_ASSET_ID.to_string(), |ptd| ptd.asset_id), destination: maybe_payment_details_destination .unwrap_or("Destination unknown".to_string()), description: maybe_payment_details_description @@ -815,6 +829,7 @@ fn filter_to_where_clause(req: &ListPaymentsRequest) -> (String, Vec { // Use the lockup address if it's incoming, else use the claim address + where_clause.push("cs.id IS NOT NULL".to_string()); where_clause.push( "(cs.direction = 0 AND cs.lockup_address = ? OR cs.direction = 1 AND cs.claim_address = ?)" .to_string(), @@ -822,9 +837,19 @@ fn filter_to_where_clause(req: &ListPaymentsRequest) -> (String, Vec { - where_clause.push("pd.destination = ?".to_string()); - where_params.push(Box::new(destination)); + ListPaymentDetails::Liquid { + asset_id, + destination, + } => { + where_clause.push("COALESCE(rs.id, ss.id, cs.id) IS NULL".to_string()); + if let Some(asset_id) = asset_id { + where_clause.push("ptx.asset_id = ?".to_string()); + where_params.push(Box::new(asset_id)); + } + if let Some(destination) = destination { + where_clause.push("pd.destination = ?".to_string()); + where_params.push(Box::new(destination)); + } } } } diff --git a/lib/core/src/receive_swap.rs b/lib/core/src/receive_swap.rs index f6089439a..81d86af81 100644 --- a/lib/core/src/receive_swap.rs +++ b/lib/core/src/receive_swap.rs @@ -12,6 +12,7 @@ use crate::chain::liquid::LiquidChainService; use crate::model::PaymentState::*; use crate::model::{Config, PaymentTxData, PaymentType, ReceiveSwap}; use crate::prelude::{Swap, Transaction}; +use crate::wallet::LIQUID_BTC_ASSET_ID; use crate::{ensure_sdk, utils}; use crate::{ error::PaymentError, model::PaymentState, persist::Persister, swapper::Swapper, @@ -333,6 +334,7 @@ impl ReceiveSwapHandler { PaymentTxData { tx_id: claim_tx_id.clone(), timestamp: Some(utils::now()), + asset_id: LIQUID_BTC_ASSET_ID.to_string(), amount_sat: swap.receiver_amount_sat, fees_sat: 0, payment_type: PaymentType::Receive, diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index a754c8c84..c79014e44 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -29,6 +29,7 @@ use signer::SdkSigner; use tokio::sync::{watch, Mutex, RwLock}; use tokio::time::MissedTickBehavior; use tokio_stream::wrappers::BroadcastStream; +use wallet::LIQUID_BTC_ASSET_ID; use x509_parser::parse_x509_certificate; use crate::chain::bitcoin::BitcoinChainService; @@ -786,11 +787,12 @@ impl LiquidSdk { &self, amount_sat: u64, address: &str, + asset_id: &str, ) -> Result { let fee_rate_msat_per_vbyte = self.config.lowball_fee_rate_msat_per_vbyte(); Ok(self .onchain_wallet - .build_tx(fee_rate_msat_per_vbyte, address, amount_sat) + .build_tx(fee_rate_msat_per_vbyte, address, asset_id, amount_sat) .await? .all_fees() .values() @@ -812,8 +814,12 @@ impl LiquidSdk { user_lockup_amount_sat: u64, ) -> Result { let temp_p2tr_addr = self.get_temp_p2tr_addr(); - self.estimate_onchain_tx_fee(user_lockup_amount_sat, temp_p2tr_addr) - .await + self.estimate_onchain_tx_fee( + user_lockup_amount_sat, + temp_p2tr_addr, + LIQUID_BTC_ASSET_ID.as_str(), + ) + .await } async fn estimate_drain_tx_fee( @@ -843,13 +849,18 @@ impl LiquidSdk { &self, amount_sat: u64, address: &str, + asset_id: &str, ) -> Result { - match self.estimate_onchain_tx_fee(amount_sat, address).await { + match self + .estimate_onchain_tx_fee(amount_sat, address, asset_id) + .await + { Ok(fees_sat) => Ok(fees_sat), - Err(PaymentError::InsufficientFunds) => self - .estimate_drain_tx_fee(Some(amount_sat), Some(address)) - .await - .map_err(|_| PaymentError::InsufficientFunds), + Err(PaymentError::InsufficientFunds) if asset_id.eq(LIQUID_BTC_ASSET_ID.as_str()) => { + self.estimate_drain_tx_fee(Some(amount_sat), Some(address)) + .await + .map_err(|_| PaymentError::InsufficientFunds) + } Err(e) => Err(e), } } @@ -859,8 +870,12 @@ impl LiquidSdk { amount_sat: u64, ) -> Result { let temp_p2tr_addr = self.get_temp_p2tr_addr(); - self.estimate_onchain_tx_or_drain_tx_fee(amount_sat, temp_p2tr_addr) - .await + self.estimate_onchain_tx_or_drain_tx_fee( + amount_sat, + temp_p2tr_addr, + LIQUID_BTC_ASSET_ID.as_str(), + ) + .await } /// Prepares to pay a Lightning invoice via a submarine swap. @@ -873,6 +888,8 @@ impl LiquidSdk { /// when paying directly onchain or via amount-less BIP21. /// - [PayAmount::Drain] which uses all funds /// - [PayAmount::Receiver] which sets the amount the receiver should receive + /// * `asset_id` - The optional id of the asset to send. Should only be specified + /// when paying directly onchain or via amount-less BIP21. /// /// # Returns /// Returns a [PrepareSendResponse] containing: @@ -887,6 +904,7 @@ impl LiquidSdk { let get_info_res = self.get_info().await?; let fees_sat; let receiver_amount_sat; + let asset_id; let payment_destination; match self.parse(&req.destination).await { @@ -904,7 +922,11 @@ impl LiquidSdk { }, (_, Some(amount)) => amount, }; - + asset_id = match (liquid_address_data.asset_id.clone(), req.asset_id.clone()) { + (None, None) => LIQUID_BTC_ASSET_ID.to_string(), + (Some(asset_id), None) => asset_id, + (_, Some(asset_id)) => asset_id, + }; ensure_sdk!( liquid_address_data.network == self.config.network.into(), PaymentError::InvalidNetwork { @@ -918,6 +940,12 @@ impl LiquidSdk { (receiver_amount_sat, fees_sat) = match amount { PayAmount::Drain => { + ensure_sdk!( + asset_id.eq(LIQUID_BTC_ASSET_ID.as_str()), + PaymentError::Generic { + err: "Cannot drain to a non Liquid Bitcoin asset".to_string(), + } + ); ensure_sdk!( get_info_res.pending_receive_sat == 0 && get_info_res.pending_send_sat == 0, @@ -937,6 +965,7 @@ impl LiquidSdk { .estimate_onchain_tx_or_drain_tx_fee( amount_sat, &liquid_address_data.address, + &asset_id, ) .await?; (amount_sat, fees_sat) @@ -944,6 +973,7 @@ impl LiquidSdk { }; liquid_address_data.amount_sat = Some(receiver_amount_sat); + liquid_address_data.asset_id = Some(asset_id.clone()); payment_destination = SendDestination::LiquidAddress { address_data: liquid_address_data, }; @@ -973,10 +1003,15 @@ impl LiquidSdk { let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?; + asset_id = LIQUID_BTC_ASSET_ID.to_string(); fees_sat = match self.swapper.check_for_mrh(&invoice.bolt11)? { Some((lbtc_address, _)) => { - self.estimate_onchain_tx_or_drain_tx_fee(receiver_amount_sat, &lbtc_address) - .await? + self.estimate_onchain_tx_or_drain_tx_fee( + receiver_amount_sat, + &lbtc_address, + &asset_id, + ) + .await? } None => { let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat); @@ -1011,6 +1046,7 @@ impl LiquidSdk { let lockup_fees_sat = self .estimate_lockup_tx_or_drain_tx_fee(receiver_amount_sat + boltz_fees_total) .await?; + asset_id = LIQUID_BTC_ASSET_ID.to_string(); fees_sat = boltz_fees_total + lockup_fees_sat; payment_destination = SendDestination::Bolt12 { @@ -1023,11 +1059,7 @@ impl LiquidSdk { } }; - let payer_amount_sat = receiver_amount_sat + fees_sat; - ensure_sdk!( - payer_amount_sat <= get_info_res.balance_sat, - PaymentError::InsufficientFunds - ); + get_info_res.validate_sufficient_funds(receiver_amount_sat, fees_sat, &asset_id)?; Ok(PrepareSendResponse { destination: payment_destination, @@ -1075,6 +1107,9 @@ impl LiquidSdk { let Some(amount_sat) = liquid_address_data.amount_sat else { return Err(PaymentError::AmountMissing { err: "`amount_sat` must be present when paying to a `SendDestination::LiquidAddress`".to_string() }); }; + let Some(ref asset_id) = liquid_address_data.asset_id else { + return Err(PaymentError::Generic { err: "`asset_id` must be present when paying to a `SendDestination::LiquidAddress`".to_string() }); + }; ensure_sdk!( liquid_address_data.network == self.config.network.into(), @@ -1087,12 +1122,9 @@ impl LiquidSdk { } ); - let payer_amount_sat = amount_sat + fees_sat; - ensure_sdk!( - payer_amount_sat <= self.get_info().await?.balance_sat, - PaymentError::InsufficientFunds - ); - + self.get_info() + .await? + .validate_sufficient_funds(amount_sat, *fees_sat, asset_id)?; self.pay_liquid(liquid_address_data.clone(), amount_sat, *fees_sat, true) .await } @@ -1206,9 +1238,13 @@ impl LiquidSdk { let destination = address_data .to_uri() .unwrap_or(address_data.address.clone()); + let asset_id = address_data + .asset_id + .unwrap_or(LIQUID_BTC_ASSET_ID.to_string()); let payments = self.persister.get_payments(&ListPaymentsRequest { details: Some(ListPaymentDetails::Liquid { - destination: destination.clone(), + asset_id: Some(asset_id.clone()), + destination: Some(destination.clone()), }), ..Default::default() })?; @@ -1222,6 +1258,7 @@ impl LiquidSdk { .build_tx_or_drain_tx( self.config.lowball_fee_rate_msat_per_vbyte(), &address_data.address, + &asset_id, receiver_amount_sat, ) .await?; @@ -1247,6 +1284,7 @@ impl LiquidSdk { payment_type: PaymentType::Send, is_confirmed: false, unblinding_data: None, + asset_id: asset_id.clone(), }; let description = address_data.message; @@ -1264,6 +1302,7 @@ impl LiquidSdk { self.emit_payment_updated(Some(tx_id)).await?; // Emit Pending event let payment_details = PaymentDetails::Liquid { + asset_id, destination, description: description.unwrap_or("Liquid transfer".to_string()), }; @@ -1734,14 +1773,16 @@ impl LiquidSdk { /// # Arguments /// /// * `req` - the [PrepareReceiveRequest] containing: - /// * `payer_amount_sat` - the amount in satoshis to be paid by the payer /// * `payment_method` - the supported payment methods; either an invoice, a Liquid address or a Bitcoin address + /// * `payer_amount_sat` - the amount in satoshis to be paid by the payer + /// * `asset_id` - the id of the asset to receive when receiving via Liquid address pub async fn prepare_receive_payment( &self, req: &PrepareReceiveRequest, ) -> Result { self.ensure_is_started().await?; + let mut asset_id = None; let mut min_payer_amount_sat = None; let mut max_payer_amount_sat = None; let mut swapper_feerate = None; @@ -1791,6 +1832,10 @@ impl LiquidSdk { debug!("Preparing Chain Receive Swap with: payer_amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}"); } PaymentMethod::LiquidAddress => { + asset_id = req + .asset_id + .clone() + .or(Some(LIQUID_BTC_ASSET_ID.to_string())); fees_sat = 0; let payer_amount_sat = req.payer_amount_sat; debug!("Preparing Liquid Receive Swap with: amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}"); @@ -1799,6 +1844,7 @@ impl LiquidSdk { Ok(PrepareReceiveResponse { payer_amount_sat: req.payer_amount_sat, + asset_id, fees_sat, payment_method: req.payment_method.clone(), min_payer_amount_sat, @@ -1830,6 +1876,7 @@ impl LiquidSdk { let PrepareReceiveResponse { payment_method, payer_amount_sat: amount_sat, + asset_id, fees_sat, .. } = &req.prepare_response; @@ -1859,6 +1906,9 @@ impl LiquidSdk { } PaymentMethod::BitcoinAddress => self.receive_onchain(*amount_sat, *fees_sat).await, PaymentMethod::LiquidAddress => { + let Some(asset_id) = asset_id else { + return Err(PaymentError::Generic { err: "`asset_id` must be specified when `PaymentMethod::LiquidAddress` is used.".to_string() }); + }; let address = self.onchain_wallet.next_unused_address().await?.to_string(); let receive_destination = match amount_sat { @@ -1866,7 +1916,7 @@ impl LiquidSdk { address: address.to_string(), network: self.config.network.into(), amount_sat: Some(*amount_sat), - asset_id: Some(AssetId::LIQUID_BTC.to_hex()), + asset_id: Some(asset_id.clone()), label: None, message: req.description.clone(), } @@ -2284,6 +2334,7 @@ impl LiquidSdk { .prepare_receive_payment(&PrepareReceiveRequest { payment_method: PaymentMethod::BitcoinAddress, payer_amount_sat: Some(req.amount_sat), + asset_id: None, }) .await?; @@ -2495,22 +2546,16 @@ impl LiquidSdk { } async fn update_wallet_info(&self) -> Result<()> { - let transactions = self.onchain_wallet.transactions().await?; - let wallet_amount_sat = transactions + let mut balance = self.onchain_wallet.balance().await?; + let balance_sat = balance.remove(&AssetId::LIQUID_BTC).unwrap_or(0); + let asset_balances = balance .into_iter() - .map(|tx| { - tx.balance - .into_iter() - .filter_map(|(asset_id, balance)| { - if asset_id == lwk_wollet::elements::AssetId::LIQUID_BTC { - return Some(balance); - } - None - }) - .sum::() + .map(|(asset_id, balance)| AssetBalance { + asset_id: asset_id.to_hex(), + balance, }) - .sum::(); - debug!("Onchain wallet balance: {wallet_amount_sat} sats"); + .collect::>(); + debug!("Onchain wallet balance: {balance_sat} sats"); let mut pending_send_sat = 0; let mut pending_receive_sat = 0; @@ -2524,21 +2569,24 @@ impl LiquidSdk { })?; for payment in payments { - match payment.payment_type { - PaymentType::Send => match payment.details.get_refund_tx_amount_sat() { - Some(refund_tx_amount_sat) => pending_receive_sat += refund_tx_amount_sat, - None => pending_send_sat += payment.amount_sat, - }, - PaymentType::Receive => pending_receive_sat += payment.amount_sat, + if payment.details.is_liquid_btc_asset_id() { + match payment.payment_type { + PaymentType::Send => match payment.details.get_refund_tx_amount_sat() { + Some(refund_tx_amount_sat) => pending_receive_sat += refund_tx_amount_sat, + None => pending_send_sat += payment.amount_sat, + }, + PaymentType::Receive => pending_receive_sat += payment.amount_sat, + } } } let info_response = GetInfoResponse { - balance_sat: wallet_amount_sat as u64, + balance_sat, pending_send_sat, pending_receive_sat, fingerprint: self.onchain_wallet.fingerprint()?, pubkey: self.onchain_wallet.pubkey()?, + asset_balances, }; self.persister.set_wallet_info(&info_response) } @@ -2784,6 +2832,7 @@ impl LiquidSdk { .prepare_send_payment(&PrepareSendRequest { destination: data.pr.clone(), amount: None, + asset_id: None, }) .await .map_err(|e| LnUrlPayError::Generic { err: e.to_string() })?; @@ -2927,6 +2976,7 @@ impl LiquidSdk { PrepareReceiveRequest { payment_method: PaymentMethod::Lightning, payer_amount_sat: Some(req.amount_msat / 1_000), + asset_id: None, } }) .await?; diff --git a/lib/core/src/send_swap.rs b/lib/core/src/send_swap.rs index 248afe298..fba44084d 100644 --- a/lib/core/src/send_swap.rs +++ b/lib/core/src/send_swap.rs @@ -21,7 +21,7 @@ use crate::persist::model::PaymentTxDetails; use crate::prelude::{PaymentTxData, PaymentType, Swap}; use crate::recover::recoverer::Recoverer; use crate::swapper::Swapper; -use crate::wallet::OnchainWallet; +use crate::wallet::{OnchainWallet, LIQUID_BTC_ASSET_ID}; use crate::{ensure_sdk, utils}; use crate::{ error::PaymentError, @@ -198,6 +198,7 @@ impl SendSwapHandler { .build_tx_or_drain_tx( self.config.lowball_fee_rate_msat_per_vbyte(), &create_response.address, + LIQUID_BTC_ASSET_ID.as_str(), create_response.expected_amount, ) .await?; @@ -231,6 +232,7 @@ impl SendSwapHandler { PaymentTxData { tx_id: lockup_tx_id.clone(), timestamp: Some(utils::now()), + asset_id: LIQUID_BTC_ASSET_ID.to_string(), amount_sat: swap.payer_amount_sat, fees_sat: lockup_tx_fees_sat, payment_type: PaymentType::Send, diff --git a/lib/core/src/test_utils/persist.rs b/lib/core/src/test_utils/persist.rs index 8e632b4be..bb1c76c96 100644 --- a/lib/core/src/test_utils/persist.rs +++ b/lib/core/src/test_utils/persist.rs @@ -14,6 +14,7 @@ use crate::{ model::{PaymentState, PaymentTxData, PaymentType, ReceiveSwap, SendSwap}, test_utils::generate_random_string, utils, + wallet::LIQUID_BTC_ASSET_ID, }; fn new_secret_key() -> SecretKey { @@ -158,6 +159,7 @@ pub(crate) fn new_payment_tx_data(payment_type: PaymentType) -> PaymentTxData { PaymentTxData { tx_id: generate_random_string(4), timestamp: None, + asset_id: LIQUID_BTC_ASSET_ID.to_string(), amount_sat: 0, fees_sat: 0, payment_type, diff --git a/lib/core/src/test_utils/wallet.rs b/lib/core/src/test_utils/wallet.rs index bf828e7f6..176739bb2 100644 --- a/lib/core/src/test_utils/wallet.rs +++ b/lib/core/src/test_utils/wallet.rs @@ -1,6 +1,10 @@ #![cfg(test)] -use std::{collections::HashMap, str::FromStr, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + str::FromStr, + sync::Arc, +}; use crate::{ error::PaymentError, @@ -19,7 +23,7 @@ use lwk_wollet::{ self, bip32::{DerivationPath, Xpriv, Xpub}, }, - elements::{hex::ToHex, Address, Transaction, Txid}, + elements::{hex::ToHex, Address, AssetId, Transaction, Txid}, elements_miniscript::{slip77::MasterBlindingKey, ToPublicKey as _}, secp256k1::{All, Message}, Tip, WalletTx, @@ -43,6 +47,10 @@ impl MockWallet { #[async_trait] impl OnchainWallet for MockWallet { + async fn balance(&self) -> Result, PaymentError> { + Ok(Default::default()) + } + async fn transactions(&self) -> Result, PaymentError> { Ok(vec![]) } @@ -55,6 +63,7 @@ impl OnchainWallet for MockWallet { &self, _fee_rate: Option, _recipient_address: &str, + _asset_id: &str, _amount_sat: u64, ) -> Result { Ok(TEST_LIQUID_TX.clone()) @@ -73,6 +82,7 @@ impl OnchainWallet for MockWallet { &self, _fee_rate_sats_per_kvb: Option, _recipient_address: &str, + _asset_id: &str, _amount_sat: u64, ) -> Result { Ok(TEST_LIQUID_TX.clone()) diff --git a/lib/core/src/wallet.rs b/lib/core/src/wallet.rs index fadec3823..51ecd945f 100644 --- a/lib/core/src/wallet.rs +++ b/lib/core/src/wallet.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fs::{self, create_dir_all}; use std::io::Write; use std::path::PathBuf; @@ -7,10 +7,11 @@ use std::{path::Path, str::FromStr, sync::Arc}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use boltz_client::ElementsAddress; +use lazy_static::lazy_static; use log::{debug, warn}; use lwk_common::Signer as LwkSigner; use lwk_common::{singlesig_desc, Singlesig}; -use lwk_wollet::elements::Txid; +use lwk_wollet::elements::{AssetId, Txid}; use lwk_wollet::{ elements::{hex::ToHex, Address, Transaction}, ElectrumClient, ElectrumUrl, ElementsNetwork, FsPersister, Tip, WalletTx, Wollet, @@ -33,8 +34,15 @@ use lwk_wollet::secp256k1::Message; static LN_MESSAGE_PREFIX: &[u8] = b"Lightning Signed Message:"; +lazy_static! { + pub(crate) static ref LIQUID_BTC_ASSET_ID: String = AssetId::LIQUID_BTC.to_hex(); +} + #[async_trait] pub trait OnchainWallet: Send + Sync { + /// Get the wallet balance + async fn balance(&self) -> Result, PaymentError>; + /// List all transactions in the wallet async fn transactions(&self) -> Result, PaymentError>; @@ -46,6 +54,7 @@ pub trait OnchainWallet: Send + Sync { &self, fee_rate_sats_per_kvb: Option, recipient_address: &str, + asset_id: &str, amount_sat: u64, ) -> Result; @@ -70,6 +79,7 @@ pub trait OnchainWallet: Send + Sync { &self, fee_rate_sats_per_kvb: Option, recipient_address: &str, + asset_id: &str, amount_sat: u64, ) -> Result; @@ -176,6 +186,14 @@ impl LiquidOnchainWallet { #[async_trait] impl OnchainWallet for LiquidOnchainWallet { + /// Get the wallet balance + async fn balance(&self) -> Result, PaymentError> { + let wallet = self.wallet.lock().await; + wallet.balance().map_err(|e| PaymentError::Generic { + err: format!("Failed to fetch wallet balance: {e:?}"), + }) + } + /// List all transactions in the wallet async fn transactions(&self) -> Result, PaymentError> { let wallet = self.wallet.lock().await; @@ -200,22 +218,25 @@ impl OnchainWallet for LiquidOnchainWallet { &self, fee_rate_sats_per_kvb: Option, recipient_address: &str, + asset_id: &str, amount_sat: u64, ) -> Result { let lwk_wollet = self.wallet.lock().await; - let mut pset = lwk_wollet::TxBuilder::new(self.config.network.into()) - .add_lbtc_recipient( - &ElementsAddress::from_str(recipient_address).map_err(|e| { - PaymentError::Generic { - err: format!( - "Recipient address {recipient_address} is not a valid ElementsAddress: {e:?}" - ), - } - })?, - amount_sat, - )? - .fee_rate(fee_rate_sats_per_kvb) - .finish(&lwk_wollet)?; + let address = + ElementsAddress::from_str(recipient_address).map_err(|e| PaymentError::Generic { + err: format!( + "Recipient address {recipient_address} is not a valid ElementsAddress: {e:?}" + ), + })?; + let mut tx_builder = + lwk_wollet::TxBuilder::new(self.config.network.into()).fee_rate(fee_rate_sats_per_kvb); + if asset_id.eq(LIQUID_BTC_ASSET_ID.as_str()) { + tx_builder = tx_builder.add_lbtc_recipient(&address, amount_sat)?; + } else { + let asset = AssetId::from_str(asset_id)?; + tx_builder = tx_builder.add_recipient(&address, amount_sat, asset)?; + } + let mut pset = tx_builder.finish(&lwk_wollet)?; self.signer .sign(&mut pset) .map_err(|e| PaymentError::Generic { @@ -274,14 +295,20 @@ impl OnchainWallet for LiquidOnchainWallet { &self, fee_rate_sats_per_kvb: Option, recipient_address: &str, + asset_id: &str, amount_sat: u64, ) -> Result { match self - .build_tx(fee_rate_sats_per_kvb, recipient_address, amount_sat) + .build_tx( + fee_rate_sats_per_kvb, + recipient_address, + asset_id, + amount_sat, + ) .await { Ok(tx) => Ok(tx), - Err(PaymentError::InsufficientFunds) => { + Err(PaymentError::InsufficientFunds) if asset_id.eq(LIQUID_BTC_ASSET_ID.as_str()) => { warn!("Cannot build tx due to insufficient funds, attempting to build drain tx"); self.build_drain_tx(fee_rate_sats_per_kvb, recipient_address, Some(amount_sat)) .await diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 26f9c0653..97e525015 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -1378,6 +1378,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + AssetBalance dco_decode_asset_balance(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 AssetBalance( + assetId: dco_decode_String(arr[0]), + balance: dco_decode_u_64(arr[1]), + ); + } + @protected BackupRequest dco_decode_backup_request(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1881,13 +1892,14 @@ 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 != 6) throw Exception('unexpected arr length: expect 6 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]), + assetBalances: dco_decode_list_asset_balance(arr[5]), ); } @@ -2015,6 +2027,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_String).toList(); } + @protected + List dco_decode_list_asset_balance(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_asset_balance).toList(); + } + @protected List dco_decode_list_external_input_parser(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2057,7 +2075,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { switch (raw[0]) { case 0: return ListPaymentDetails_Liquid( - destination: dco_decode_String(raw[1]), + assetId: dco_decode_opt_String(raw[1]), + destination: dco_decode_opt_String(raw[2]), ); case 1: return ListPaymentDetails_Bitcoin( @@ -2693,6 +2712,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return PaymentDetails_Liquid( destination: dco_decode_String(raw[1]), description: dco_decode_String(raw[2]), + assetId: dco_decode_String(raw[3]), ); case 2: return PaymentDetails_Bitcoin( @@ -2873,10 +2893,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareReceiveRequest dco_decode_prepare_receive_request(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}'); + if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); return PrepareReceiveRequest( - payerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[0]), - paymentMethod: dco_decode_payment_method(arr[1]), + paymentMethod: dco_decode_payment_method(arr[0]), + payerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[1]), + assetId: dco_decode_opt_String(arr[2]), ); } @@ -2884,14 +2905,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareReceiveResponse dco_decode_prepare_receive_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); return PrepareReceiveResponse( paymentMethod: dco_decode_payment_method(arr[0]), payerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[1]), - feesSat: dco_decode_u_64(arr[2]), - minPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[3]), - maxPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[4]), - swapperFeerate: dco_decode_opt_box_autoadd_f_64(arr[5]), + assetId: dco_decode_opt_String(arr[2]), + feesSat: dco_decode_u_64(arr[3]), + minPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[4]), + maxPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[5]), + swapperFeerate: dco_decode_opt_box_autoadd_f_64(arr[6]), ); } @@ -2923,10 +2945,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareSendRequest dco_decode_prepare_send_request(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}'); + if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); return PrepareSendRequest( destination: dco_decode_String(arr[0]), amount: dco_decode_opt_box_autoadd_pay_amount(arr[1]), + assetId: dco_decode_opt_String(arr[2]), ); } @@ -3395,6 +3418,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + AssetBalance sse_decode_asset_balance(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_assetId = sse_decode_String(deserializer); + var var_balance = sse_decode_u_64(deserializer); + return AssetBalance(assetId: var_assetId, balance: var_balance); + } + @protected BackupRequest sse_decode_backup_request(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3901,12 +3932,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var var_pendingReceiveSat = sse_decode_u_64(deserializer); var var_fingerprint = sse_decode_String(deserializer); var var_pubkey = sse_decode_String(deserializer); + var var_assetBalances = sse_decode_list_asset_balance(deserializer); return GetInfoResponse( balanceSat: var_balanceSat, pendingSendSat: var_pendingSendSat, pendingReceiveSat: var_pendingReceiveSat, fingerprint: var_fingerprint, - pubkey: var_pubkey); + pubkey: var_pubkey, + assetBalances: var_assetBalances); } @protected @@ -4030,6 +4063,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_asset_balance(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_asset_balance(deserializer)); + } + return ans_; + } + @protected List sse_decode_list_external_input_parser(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4109,8 +4154,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var tag_ = sse_decode_i_32(deserializer); switch (tag_) { case 0: - var var_destination = sse_decode_String(deserializer); - return ListPaymentDetails_Liquid(destination: var_destination); + var var_assetId = sse_decode_opt_String(deserializer); + var var_destination = sse_decode_opt_String(deserializer); + return ListPaymentDetails_Liquid(assetId: var_assetId, destination: var_destination); case 1: var var_address = sse_decode_String(deserializer); return ListPaymentDetails_Bitcoin(address: var_address); @@ -4857,7 +4903,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 1: var var_destination = sse_decode_String(deserializer); var var_description = sse_decode_String(deserializer); - return PaymentDetails_Liquid(destination: var_destination, description: var_description); + var var_assetId = sse_decode_String(deserializer); + return PaymentDetails_Liquid( + destination: var_destination, description: var_description, assetId: var_assetId); case 2: var var_swapId = sse_decode_String(deserializer); var var_description = sse_decode_String(deserializer); @@ -5028,9 +5076,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected PrepareReceiveRequest sse_decode_prepare_receive_request(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - var var_payerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); var var_paymentMethod = sse_decode_payment_method(deserializer); - return PrepareReceiveRequest(payerAmountSat: var_payerAmountSat, paymentMethod: var_paymentMethod); + var var_payerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); + var var_assetId = sse_decode_opt_String(deserializer); + return PrepareReceiveRequest( + paymentMethod: var_paymentMethod, payerAmountSat: var_payerAmountSat, assetId: var_assetId); } @protected @@ -5038,6 +5088,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs var var_paymentMethod = sse_decode_payment_method(deserializer); var var_payerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); + var var_assetId = sse_decode_opt_String(deserializer); var var_feesSat = sse_decode_u_64(deserializer); var var_minPayerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); var var_maxPayerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); @@ -5045,6 +5096,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return PrepareReceiveResponse( paymentMethod: var_paymentMethod, payerAmountSat: var_payerAmountSat, + assetId: var_assetId, feesSat: var_feesSat, minPayerAmountSat: var_minPayerAmountSat, maxPayerAmountSat: var_maxPayerAmountSat, @@ -5077,7 +5129,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs var var_destination = sse_decode_String(deserializer); var var_amount = sse_decode_opt_box_autoadd_pay_amount(deserializer); - return PrepareSendRequest(destination: var_destination, amount: var_amount); + var var_assetId = sse_decode_opt_String(deserializer); + return PrepareSendRequest(destination: var_destination, amount: var_amount, assetId: var_assetId); } @protected @@ -5601,6 +5654,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_asset_balance(AssetBalance self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.assetId, serializer); + sse_encode_u_64(self.balance, serializer); + } + @protected void sse_encode_backup_request(BackupRequest self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6069,6 +6129,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_64(self.pendingReceiveSat, serializer); sse_encode_String(self.fingerprint, serializer); sse_encode_String(self.pubkey, serializer); + sse_encode_list_asset_balance(self.assetBalances, serializer); } @protected @@ -6172,6 +6233,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_asset_balance(List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_asset_balance(item, serializer); + } + } + @protected void sse_encode_list_external_input_parser(List self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6230,9 +6300,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_list_payment_details(ListPaymentDetails self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs switch (self) { - case ListPaymentDetails_Liquid(destination: final destination): + case ListPaymentDetails_Liquid(assetId: final assetId, destination: final destination): sse_encode_i_32(0, serializer); - sse_encode_String(destination, serializer); + sse_encode_opt_String(assetId, serializer); + sse_encode_opt_String(destination, serializer); case ListPaymentDetails_Bitcoin(address: final address): sse_encode_i_32(1, serializer); sse_encode_String(address, serializer); @@ -6832,10 +6903,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_opt_box_autoadd_ln_url_info(lnurlInfo, serializer); sse_encode_opt_String(refundTxId, serializer); sse_encode_opt_box_autoadd_u_64(refundTxAmountSat, serializer); - case PaymentDetails_Liquid(destination: final destination, description: final description): + case PaymentDetails_Liquid( + destination: final destination, + description: final description, + assetId: final assetId + ): sse_encode_i_32(1, serializer); sse_encode_String(destination, serializer); sse_encode_String(description, serializer); + sse_encode_String(assetId, serializer); case PaymentDetails_Bitcoin( swapId: final swapId, description: final description, @@ -6980,8 +7056,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected void sse_encode_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_opt_box_autoadd_u_64(self.payerAmountSat, serializer); sse_encode_payment_method(self.paymentMethod, serializer); + sse_encode_opt_box_autoadd_u_64(self.payerAmountSat, serializer); + sse_encode_opt_String(self.assetId, serializer); } @protected @@ -6989,6 +7066,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_payment_method(self.paymentMethod, serializer); sse_encode_opt_box_autoadd_u_64(self.payerAmountSat, serializer); + sse_encode_opt_String(self.assetId, serializer); sse_encode_u_64(self.feesSat, serializer); sse_encode_opt_box_autoadd_u_64(self.minPayerAmountSat, serializer); sse_encode_opt_box_autoadd_u_64(self.maxPayerAmountSat, serializer); @@ -7016,6 +7094,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_String(self.destination, serializer); sse_encode_opt_box_autoadd_pay_amount(self.amount, serializer); + sse_encode_opt_String(self.assetId, serializer); } @protected diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 6a82a980a..4cef7c793 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -65,6 +65,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Amount dco_decode_amount(dynamic raw); + @protected + AssetBalance dco_decode_asset_balance(dynamic raw); + @protected BackupRequest dco_decode_backup_request(dynamic raw); @@ -302,6 +305,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_String(dynamic raw); + @protected + List dco_decode_list_asset_balance(dynamic raw); + @protected List dco_decode_list_external_input_parser(dynamic raw); @@ -656,6 +662,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Amount sse_decode_amount(SseDeserializer deserializer); + @protected + AssetBalance sse_decode_asset_balance(SseDeserializer deserializer); + @protected BackupRequest sse_decode_backup_request(SseDeserializer deserializer); @@ -899,6 +908,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List sse_decode_list_String(SseDeserializer deserializer); + @protected + List sse_decode_list_asset_balance(SseDeserializer deserializer); + @protected List sse_decode_list_external_input_parser(SseDeserializer deserializer); @@ -1708,6 +1720,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ans; } + @protected + ffi.Pointer cst_encode_list_asset_balance(List raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ans = wire.cst_new_list_asset_balance(raw.length); + for (var i = 0; i < raw.length; ++i) { + cst_api_fill_to_wire_asset_balance(raw[i], ans.ref.ptr[i]); + } + return ans; + } + @protected ffi.Pointer cst_encode_list_external_input_parser( List raw) { @@ -2011,6 +2033,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { } } + @protected + void cst_api_fill_to_wire_asset_balance(AssetBalance apiObj, wire_cst_asset_balance wireObj) { + wireObj.asset_id = cst_encode_String(apiObj.assetId); + wireObj.balance = cst_encode_u_64(apiObj.balance); + } + @protected void cst_api_fill_to_wire_backup_request(BackupRequest apiObj, wire_cst_backup_request wireObj) { wireObj.backup_path = cst_encode_opt_String(apiObj.backupPath); @@ -2410,6 +2438,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.pending_receive_sat = cst_encode_u_64(apiObj.pendingReceiveSat); wireObj.fingerprint = cst_encode_String(apiObj.fingerprint); wireObj.pubkey = cst_encode_String(apiObj.pubkey); + wireObj.asset_balances = cst_encode_list_asset_balance(apiObj.assetBalances); } @protected @@ -2516,8 +2545,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void cst_api_fill_to_wire_list_payment_details( ListPaymentDetails apiObj, wire_cst_list_payment_details wireObj) { if (apiObj is ListPaymentDetails_Liquid) { - var pre_destination = cst_encode_String(apiObj.destination); + var pre_asset_id = cst_encode_opt_String(apiObj.assetId); + var pre_destination = cst_encode_opt_String(apiObj.destination); wireObj.tag = 0; + wireObj.kind.Liquid.asset_id = pre_asset_id; wireObj.kind.Liquid.destination = pre_destination; return; } @@ -2950,9 +2981,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { if (apiObj is PaymentDetails_Liquid) { var pre_destination = cst_encode_String(apiObj.destination); var pre_description = cst_encode_String(apiObj.description); + var pre_asset_id = cst_encode_String(apiObj.assetId); wireObj.tag = 1; wireObj.kind.Liquid.destination = pre_destination; wireObj.kind.Liquid.description = pre_description; + wireObj.kind.Liquid.asset_id = pre_asset_id; return; } if (apiObj is PaymentDetails_Bitcoin) { @@ -3131,8 +3164,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_prepare_receive_request( PrepareReceiveRequest apiObj, wire_cst_prepare_receive_request wireObj) { - wireObj.payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.payerAmountSat); wireObj.payment_method = cst_encode_payment_method(apiObj.paymentMethod); + wireObj.payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.payerAmountSat); + wireObj.asset_id = cst_encode_opt_String(apiObj.assetId); } @protected @@ -3140,6 +3174,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PrepareReceiveResponse apiObj, wire_cst_prepare_receive_response wireObj) { wireObj.payment_method = cst_encode_payment_method(apiObj.paymentMethod); wireObj.payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.payerAmountSat); + wireObj.asset_id = cst_encode_opt_String(apiObj.assetId); wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); wireObj.min_payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.minPayerAmountSat); wireObj.max_payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.maxPayerAmountSat); @@ -3167,6 +3202,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PrepareSendRequest apiObj, wire_cst_prepare_send_request wireObj) { wireObj.destination = cst_encode_String(apiObj.destination); wireObj.amount = cst_encode_opt_box_autoadd_pay_amount(apiObj.amount); + wireObj.asset_id = cst_encode_opt_String(apiObj.assetId); } @protected @@ -3520,6 +3556,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_amount(Amount self, SseSerializer serializer); + @protected + void sse_encode_asset_balance(AssetBalance self, SseSerializer serializer); + @protected void sse_encode_backup_request(BackupRequest self, SseSerializer serializer); @@ -3770,6 +3809,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_list_String(List self, SseSerializer serializer); + @protected + void sse_encode_list_asset_balance(List self, SseSerializer serializer); + @protected void sse_encode_list_external_input_parser(List self, SseSerializer serializer); @@ -5500,6 +5542,20 @@ class RustLibWire implements BaseWire { late final _cst_new_list_String = _cst_new_list_StringPtr.asFunction Function(int)>(); + ffi.Pointer cst_new_list_asset_balance( + int len, + ) { + return _cst_new_list_asset_balance( + len, + ); + } + + late final _cst_new_list_asset_balancePtr = + _lookup Function(ffi.Int32)>>( + 'frbgen_breez_liquid_cst_new_list_asset_balance'); + late final _cst_new_list_asset_balance = + _cst_new_list_asset_balancePtr.asFunction Function(int)>(); + ffi.Pointer cst_new_list_external_input_parser( int len, ) { @@ -5786,6 +5842,8 @@ final class wire_cst_list_payment_state extends ffi.Struct { } final class wire_cst_ListPaymentDetails_Liquid extends ffi.Struct { + external ffi.Pointer asset_id; + external ffi.Pointer destination; } @@ -6176,10 +6234,12 @@ final class wire_cst_prepare_pay_onchain_request extends ffi.Struct { } final class wire_cst_prepare_receive_request extends ffi.Struct { - external ffi.Pointer payer_amount_sat; - @ffi.Int32() external int payment_method; + + external ffi.Pointer payer_amount_sat; + + external ffi.Pointer asset_id; } final class wire_cst_prepare_refund_request extends ffi.Struct { @@ -6195,6 +6255,8 @@ final class wire_cst_prepare_send_request extends ffi.Struct { external ffi.Pointer destination; external ffi.Pointer amount; + + external ffi.Pointer asset_id; } final class wire_cst_prepare_receive_response extends ffi.Struct { @@ -6203,6 +6265,8 @@ final class wire_cst_prepare_receive_response extends ffi.Struct { external ffi.Pointer payer_amount_sat; + external ffi.Pointer asset_id; + @ffi.Uint64() external int fees_sat; @@ -6347,6 +6411,8 @@ final class wire_cst_PaymentDetails_Liquid extends ffi.Struct { external ffi.Pointer destination; external ffi.Pointer description; + + external ffi.Pointer asset_id; } final class wire_cst_PaymentDetails_Bitcoin extends ffi.Struct { @@ -6550,6 +6616,20 @@ final class wire_cst_symbol extends ffi.Struct { external ffi.Pointer position; } +final class wire_cst_asset_balance extends ffi.Struct { + external ffi.Pointer asset_id; + + @ffi.Uint64() + external int balance; +} + +final class wire_cst_list_asset_balance extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + final class wire_cst_localized_name extends ffi.Struct { external ffi.Pointer locale; @@ -6664,6 +6744,8 @@ final class wire_cst_get_info_response extends ffi.Struct { external ffi.Pointer fingerprint; external ffi.Pointer pubkey; + + external ffi.Pointer asset_balances; } final class wire_cst_InputType_BitcoinAddress extends ffi.Struct { diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 97237af56..dec78414d 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -28,6 +28,28 @@ class AcceptPaymentProposedFeesRequest { response == other.response; } +/// An asset balance to denote the balance for each asset. +class AssetBalance { + final String assetId; + final BigInt balance; + + const AssetBalance({ + required this.assetId, + required this.balance, + }); + + @override + int get hashCode => assetId.hashCode ^ balance.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AssetBalance && + runtimeType == other.runtimeType && + assetId == other.assetId && + balance == other.balance; +} + /// An argument when calling [crate::sdk::LiquidSdk::backup]. class BackupRequest { /// Path to the backup. @@ -325,12 +347,16 @@ class GetInfoResponse { /// The wallet's pubkey. Used to verify signed messages. final String pubkey; + /// Asset balances of non Liquid Bitcoin assets + final List assetBalances; + const GetInfoResponse({ required this.balanceSat, required this.pendingSendSat, required this.pendingReceiveSat, required this.fingerprint, required this.pubkey, + required this.assetBalances, }); @override @@ -339,7 +365,8 @@ class GetInfoResponse { pendingSendSat.hashCode ^ pendingReceiveSat.hashCode ^ fingerprint.hashCode ^ - pubkey.hashCode; + pubkey.hashCode ^ + assetBalances.hashCode; @override bool operator ==(Object other) => @@ -350,7 +377,8 @@ class GetInfoResponse { pendingSendSat == other.pendingSendSat && pendingReceiveSat == other.pendingReceiveSat && fingerprint == other.fingerprint && - pubkey == other.pubkey; + pubkey == other.pubkey && + assetBalances == other.assetBalances; } @freezed @@ -430,7 +458,8 @@ sealed class ListPaymentDetails with _$ListPaymentDetails { /// The Liquid BIP21 URI or address of the payment const factory ListPaymentDetails.liquid({ - required String destination, + String? assetId, + String? destination, }) = ListPaymentDetails_Liquid; /// The Bitcoin address of the payment @@ -807,6 +836,9 @@ sealed class PaymentDetails with _$PaymentDetails { /// Represents the BIP21 `message` field required String description, + + /// The asset id + required String assetId, }) = PaymentDetails_Liquid; /// Swapping to or from the Bitcoin chain @@ -1096,30 +1128,41 @@ class PreparePayOnchainResponse { /// An argument when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. class PrepareReceiveRequest { - final BigInt? payerAmountSat; final PaymentMethod paymentMethod; + /// The payer amount in satoshi units. If receiving a non Liquid Bitcoin asset, this is the units + /// of the asset to receive. + final BigInt? payerAmountSat; + + /// The id of the asset to receive when the 'payment_method' is Liquid. + /// + /// Defaults to Liquid Bitcoin. + final String? assetId; + const PrepareReceiveRequest({ - this.payerAmountSat, required this.paymentMethod, + this.payerAmountSat, + this.assetId, }); @override - int get hashCode => payerAmountSat.hashCode ^ paymentMethod.hashCode; + int get hashCode => paymentMethod.hashCode ^ payerAmountSat.hashCode ^ assetId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is PrepareReceiveRequest && runtimeType == other.runtimeType && + paymentMethod == other.paymentMethod && payerAmountSat == other.payerAmountSat && - paymentMethod == other.paymentMethod; + assetId == other.assetId; } /// Returned when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. class PrepareReceiveResponse { final PaymentMethod paymentMethod; final BigInt? payerAmountSat; + final String? assetId; /// Generally represents the total fees that would be paid to send or receive this payment. /// @@ -1149,6 +1192,7 @@ class PrepareReceiveResponse { const PrepareReceiveResponse({ required this.paymentMethod, this.payerAmountSat, + this.assetId, required this.feesSat, this.minPayerAmountSat, this.maxPayerAmountSat, @@ -1159,6 +1203,7 @@ class PrepareReceiveResponse { int get hashCode => paymentMethod.hashCode ^ payerAmountSat.hashCode ^ + assetId.hashCode ^ feesSat.hashCode ^ minPayerAmountSat.hashCode ^ maxPayerAmountSat.hashCode ^ @@ -1171,6 +1216,7 @@ class PrepareReceiveResponse { runtimeType == other.runtimeType && paymentMethod == other.paymentMethod && payerAmountSat == other.payerAmountSat && + assetId == other.assetId && feesSat == other.feesSat && minPayerAmountSat == other.minPayerAmountSat && maxPayerAmountSat == other.maxPayerAmountSat && @@ -1242,13 +1288,18 @@ class PrepareSendRequest { /// where no amount is specified, or when the caller wishes to drain final PayAmount? amount; + /// Should only be set when paying directly onchain or to a BIP21 URI + /// where no asset id is specified + final String? assetId; + const PrepareSendRequest({ required this.destination, this.amount, + this.assetId, }); @override - int get hashCode => destination.hashCode ^ amount.hashCode; + int get hashCode => destination.hashCode ^ amount.hashCode ^ assetId.hashCode; @override bool operator ==(Object other) => @@ -1256,7 +1307,8 @@ class PrepareSendRequest { other is PrepareSendRequest && runtimeType == other.runtimeType && destination == other.destination && - amount == other.amount; + amount == other.amount && + assetId == other.assetId; } /// Returned when calling [crate::sdk::LiquidSdk::prepare_send_payment]. diff --git a/packages/dart/lib/src/model.freezed.dart b/packages/dart/lib/src/model.freezed.dart index 8b3d20ab0..ebf24d5ec 100644 --- a/packages/dart/lib/src/model.freezed.dart +++ b/packages/dart/lib/src/model.freezed.dart @@ -170,7 +170,7 @@ abstract class _$$ListPaymentDetails_LiquidImplCopyWith<$Res> { _$ListPaymentDetails_LiquidImpl value, $Res Function(_$ListPaymentDetails_LiquidImpl) then) = __$$ListPaymentDetails_LiquidImplCopyWithImpl<$Res>; @useResult - $Res call({String destination}); + $Res call({String? assetId, String? destination}); } /// @nodoc @@ -186,13 +186,18 @@ class __$$ListPaymentDetails_LiquidImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? destination = null, + Object? assetId = freezed, + Object? destination = freezed, }) { return _then(_$ListPaymentDetails_LiquidImpl( - destination: null == destination + assetId: freezed == assetId + ? _value.assetId + : assetId // ignore: cast_nullable_to_non_nullable + as String?, + destination: freezed == destination ? _value.destination : destination // ignore: cast_nullable_to_non_nullable - as String, + as String?, )); } } @@ -200,14 +205,16 @@ class __$$ListPaymentDetails_LiquidImplCopyWithImpl<$Res> /// @nodoc class _$ListPaymentDetails_LiquidImpl extends ListPaymentDetails_Liquid { - const _$ListPaymentDetails_LiquidImpl({required this.destination}) : super._(); + const _$ListPaymentDetails_LiquidImpl({this.assetId, this.destination}) : super._(); @override - final String destination; + final String? assetId; + @override + final String? destination; @override String toString() { - return 'ListPaymentDetails.liquid(destination: $destination)'; + return 'ListPaymentDetails.liquid(assetId: $assetId, destination: $destination)'; } @override @@ -215,11 +222,12 @@ class _$ListPaymentDetails_LiquidImpl extends ListPaymentDetails_Liquid { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ListPaymentDetails_LiquidImpl && + (identical(other.assetId, assetId) || other.assetId == assetId) && (identical(other.destination, destination) || other.destination == destination)); } @override - int get hashCode => Object.hash(runtimeType, destination); + int get hashCode => Object.hash(runtimeType, assetId, destination); /// Create a copy of ListPaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -231,11 +239,12 @@ class _$ListPaymentDetails_LiquidImpl extends ListPaymentDetails_Liquid { } abstract class ListPaymentDetails_Liquid extends ListPaymentDetails { - const factory ListPaymentDetails_Liquid({required final String destination}) = + const factory ListPaymentDetails_Liquid({final String? assetId, final String? destination}) = _$ListPaymentDetails_LiquidImpl; const ListPaymentDetails_Liquid._() : super._(); - String get destination; + String? get assetId; + String? get destination; /// Create a copy of ListPaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -1004,7 +1013,7 @@ abstract class _$$PaymentDetails_LiquidImplCopyWith<$Res> implements $PaymentDet __$$PaymentDetails_LiquidImplCopyWithImpl<$Res>; @override @useResult - $Res call({String destination, String description}); + $Res call({String destination, String description, String assetId}); } /// @nodoc @@ -1022,6 +1031,7 @@ class __$$PaymentDetails_LiquidImplCopyWithImpl<$Res> $Res call({ Object? destination = null, Object? description = null, + Object? assetId = null, }) { return _then(_$PaymentDetails_LiquidImpl( destination: null == destination @@ -1032,6 +1042,10 @@ class __$$PaymentDetails_LiquidImplCopyWithImpl<$Res> ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, + assetId: null == assetId + ? _value.assetId + : assetId // ignore: cast_nullable_to_non_nullable + as String, )); } } @@ -1039,7 +1053,9 @@ class __$$PaymentDetails_LiquidImplCopyWithImpl<$Res> /// @nodoc class _$PaymentDetails_LiquidImpl extends PaymentDetails_Liquid { - const _$PaymentDetails_LiquidImpl({required this.destination, required this.description}) : super._(); + const _$PaymentDetails_LiquidImpl( + {required this.destination, required this.description, required this.assetId}) + : super._(); /// Represents either a Liquid BIP21 URI or pure address @override @@ -1049,9 +1065,13 @@ class _$PaymentDetails_LiquidImpl extends PaymentDetails_Liquid { @override final String description; + /// The asset id + @override + final String assetId; + @override String toString() { - return 'PaymentDetails.liquid(destination: $destination, description: $description)'; + return 'PaymentDetails.liquid(destination: $destination, description: $description, assetId: $assetId)'; } @override @@ -1060,11 +1080,12 @@ class _$PaymentDetails_LiquidImpl extends PaymentDetails_Liquid { (other.runtimeType == runtimeType && other is _$PaymentDetails_LiquidImpl && (identical(other.destination, destination) || other.destination == destination) && - (identical(other.description, description) || other.description == description)); + (identical(other.description, description) || other.description == description) && + (identical(other.assetId, assetId) || other.assetId == assetId)); } @override - int get hashCode => Object.hash(runtimeType, destination, description); + int get hashCode => Object.hash(runtimeType, destination, description, assetId); /// Create a copy of PaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -1077,7 +1098,9 @@ class _$PaymentDetails_LiquidImpl extends PaymentDetails_Liquid { abstract class PaymentDetails_Liquid extends PaymentDetails { const factory PaymentDetails_Liquid( - {required final String destination, required final String description}) = _$PaymentDetails_LiquidImpl; + {required final String destination, + required final String description, + required final String assetId}) = _$PaymentDetails_LiquidImpl; const PaymentDetails_Liquid._() : super._(); /// Represents either a Liquid BIP21 URI or pure address @@ -1087,6 +1110,9 @@ abstract class PaymentDetails_Liquid extends PaymentDetails { @override String get description; + /// The asset id + String get assetId; + /// Create a copy of PaymentDetails /// with the given fields replaced by the non-null parameter values. @override diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index 7a7a1ceb5..93118565c 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -1493,6 +1493,21 @@ class FlutterBreezLiquidBindings { late final _frbgen_breez_liquid_cst_new_list_String = _frbgen_breez_liquid_cst_new_list_StringPtr .asFunction Function(int)>(); + ffi.Pointer frbgen_breez_liquid_cst_new_list_asset_balance( + int len, + ) { + return _frbgen_breez_liquid_cst_new_list_asset_balance( + len, + ); + } + + late final _frbgen_breez_liquid_cst_new_list_asset_balancePtr = + _lookup Function(ffi.Int32)>>( + 'frbgen_breez_liquid_cst_new_list_asset_balance'); + late final _frbgen_breez_liquid_cst_new_list_asset_balance = + _frbgen_breez_liquid_cst_new_list_asset_balancePtr + .asFunction Function(int)>(); + ffi.Pointer frbgen_breez_liquid_cst_new_list_external_input_parser( int len, ) { @@ -4156,6 +4171,8 @@ final class wire_cst_list_payment_state extends ffi.Struct { } final class wire_cst_ListPaymentDetails_Liquid extends ffi.Struct { + external ffi.Pointer asset_id; + external ffi.Pointer destination; } @@ -4546,10 +4563,12 @@ final class wire_cst_prepare_pay_onchain_request extends ffi.Struct { } final class wire_cst_prepare_receive_request extends ffi.Struct { - external ffi.Pointer payer_amount_sat; - @ffi.Int32() external int payment_method; + + external ffi.Pointer payer_amount_sat; + + external ffi.Pointer asset_id; } final class wire_cst_prepare_refund_request extends ffi.Struct { @@ -4565,6 +4584,8 @@ final class wire_cst_prepare_send_request extends ffi.Struct { external ffi.Pointer destination; external ffi.Pointer amount; + + external ffi.Pointer asset_id; } final class wire_cst_prepare_receive_response extends ffi.Struct { @@ -4573,6 +4594,8 @@ final class wire_cst_prepare_receive_response extends ffi.Struct { external ffi.Pointer payer_amount_sat; + external ffi.Pointer asset_id; + @ffi.Uint64() external int fees_sat; @@ -4717,6 +4740,8 @@ final class wire_cst_PaymentDetails_Liquid extends ffi.Struct { external ffi.Pointer destination; external ffi.Pointer description; + + external ffi.Pointer asset_id; } final class wire_cst_PaymentDetails_Bitcoin extends ffi.Struct { @@ -4920,6 +4945,20 @@ final class wire_cst_symbol extends ffi.Struct { external ffi.Pointer position; } +final class wire_cst_asset_balance extends ffi.Struct { + external ffi.Pointer asset_id; + + @ffi.Uint64() + external int balance; +} + +final class wire_cst_list_asset_balance extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + final class wire_cst_localized_name extends ffi.Struct { external ffi.Pointer locale; @@ -5034,6 +5073,8 @@ final class wire_cst_get_info_response extends ffi.Struct { external ffi.Pointer fingerprint; external ffi.Pointer pubkey; + + external ffi.Pointer asset_balances; } final class wire_cst_InputType_BitcoinAddress extends ffi.Struct { 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..9547f6c76 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 @@ -102,6 +102,39 @@ fun asAesSuccessActionDataDecryptedList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asAssetBalance(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asBackupRequest(backupRequest: ReadableMap): BackupRequest? { if (!validateMandatoryFields( backupRequest, @@ -626,6 +659,7 @@ fun asGetInfoResponse(getInfoResponse: ReadableMap): GetInfoResponse? { "pendingReceiveSat", "fingerprint", "pubkey", + "assetBalances", ), ) ) { @@ -636,7 +670,8 @@ fun asGetInfoResponse(getInfoResponse: ReadableMap): GetInfoResponse? { val pendingReceiveSat = getInfoResponse.getDouble("pendingReceiveSat").toULong() val fingerprint = getInfoResponse.getString("fingerprint")!! val pubkey = getInfoResponse.getString("pubkey")!! - return GetInfoResponse(balanceSat, pendingSendSat, pendingReceiveSat, fingerprint, pubkey) + val assetBalances = getInfoResponse.getArray("assetBalances")?.let { asAssetBalanceList(it) }!! + return GetInfoResponse(balanceSat, pendingSendSat, pendingReceiveSat, fingerprint, pubkey, assetBalances) } fun readableMapOf(getInfoResponse: GetInfoResponse): ReadableMap = @@ -646,6 +681,7 @@ fun readableMapOf(getInfoResponse: GetInfoResponse): ReadableMap = "pendingReceiveSat" to getInfoResponse.pendingReceiveSat, "fingerprint" to getInfoResponse.fingerprint, "pubkey" to getInfoResponse.pubkey, + "assetBalances" to readableArrayOf(getInfoResponse.assetBalances), ) fun asGetInfoResponseList(arr: ReadableArray): List { @@ -1912,13 +1948,15 @@ fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveR } else { null } - return PrepareReceiveRequest(paymentMethod, payerAmountSat) + val assetId = if (hasNonNullKey(prepareReceiveRequest, "assetId")) prepareReceiveRequest.getString("assetId") else null + return PrepareReceiveRequest(paymentMethod, payerAmountSat, assetId) } fun readableMapOf(prepareReceiveRequest: PrepareReceiveRequest): ReadableMap = readableMapOf( "paymentMethod" to prepareReceiveRequest.paymentMethod.name.lowercase(), "payerAmountSat" to prepareReceiveRequest.payerAmountSat, + "assetId" to prepareReceiveRequest.assetId, ) fun asPrepareReceiveRequestList(arr: ReadableArray): List { @@ -1955,6 +1993,7 @@ fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiv } else { null } + val assetId = if (hasNonNullKey(prepareReceiveResponse, "assetId")) prepareReceiveResponse.getString("assetId") else null val minPayerAmountSat = if (hasNonNullKey( prepareReceiveResponse, @@ -1985,7 +2024,7 @@ fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiv } else { null } - return PrepareReceiveResponse(paymentMethod, feesSat, payerAmountSat, minPayerAmountSat, maxPayerAmountSat, swapperFeerate) + return PrepareReceiveResponse(paymentMethod, feesSat, payerAmountSat, assetId, minPayerAmountSat, maxPayerAmountSat, swapperFeerate) } fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap = @@ -1993,6 +2032,7 @@ fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap = "paymentMethod" to prepareReceiveResponse.paymentMethod.name.lowercase(), "feesSat" to prepareReceiveResponse.feesSat, "payerAmountSat" to prepareReceiveResponse.payerAmountSat, + "assetId" to prepareReceiveResponse.assetId, "minPayerAmountSat" to prepareReceiveResponse.minPayerAmountSat, "maxPayerAmountSat" to prepareReceiveResponse.maxPayerAmountSat, "swapperFeerate" to prepareReceiveResponse.swapperFeerate, @@ -2092,13 +2132,15 @@ fun asPrepareSendRequest(prepareSendRequest: ReadableMap): PrepareSendRequest? { } val destination = prepareSendRequest.getString("destination")!! val amount = if (hasNonNullKey(prepareSendRequest, "amount")) prepareSendRequest.getMap("amount")?.let { asPayAmount(it) } else null - return PrepareSendRequest(destination, amount) + val assetId = if (hasNonNullKey(prepareSendRequest, "assetId")) prepareSendRequest.getString("assetId") else null + return PrepareSendRequest(destination, amount, assetId) } fun readableMapOf(prepareSendRequest: PrepareSendRequest): ReadableMap = readableMapOf( "destination" to prepareSendRequest.destination, "amount" to prepareSendRequest.amount?.let { readableMapOf(it) }, + "assetId" to prepareSendRequest.assetId, ) fun asPrepareSendRequestList(arr: ReadableArray): List { @@ -2945,8 +2987,9 @@ fun asListPaymentDetails(listPaymentDetails: ReadableMap): ListPaymentDetails? { val type = listPaymentDetails.getString("type") if (type == "liquid") { - val destination = listPaymentDetails.getString("destination")!! - return ListPaymentDetails.Liquid(destination) + val assetId = if (hasNonNullKey(listPaymentDetails, "assetId")) listPaymentDetails.getString("assetId") else null + val destination = if (hasNonNullKey(listPaymentDetails, "destination")) listPaymentDetails.getString("destination") else null + return ListPaymentDetails.Liquid(assetId, destination) } if (type == "bitcoin") { val address = listPaymentDetails.getString("address")!! @@ -2960,6 +3003,7 @@ fun readableMapOf(listPaymentDetails: ListPaymentDetails): ReadableMap? { when (listPaymentDetails) { is ListPaymentDetails.Liquid -> { pushToMap(map, "type", "liquid") + pushToMap(map, "assetId", listPaymentDetails.assetId) pushToMap(map, "destination", listPaymentDetails.destination) } is ListPaymentDetails.Bitcoin -> { @@ -3210,9 +3254,10 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { ) } if (type == "liquid") { + val assetId = paymentDetails.getString("assetId")!! val destination = paymentDetails.getString("destination")!! val description = paymentDetails.getString("description")!! - return PaymentDetails.Liquid(destination, description) + return PaymentDetails.Liquid(assetId, destination, description) } if (type == "bitcoin") { val swapId = paymentDetails.getString("swapId")!! @@ -3250,6 +3295,7 @@ fun readableMapOf(paymentDetails: PaymentDetails): ReadableMap? { } is PaymentDetails.Liquid -> { pushToMap(map, "type", "liquid") + pushToMap(map, "assetId", paymentDetails.assetId) pushToMap(map, "destination", paymentDetails.destination) pushToMap(map, "description", paymentDetails.description) } @@ -3576,6 +3622,7 @@ fun pushToArray( ) { when (value) { null -> array.pushNull() + is AssetBalance -> array.pushMap(readableMapOf(value)) is ExternalInputParser -> array.pushMap(readableMapOf(value)) is FiatCurrency -> array.pushMap(readableMapOf(value)) is LnOfferBlindedPath -> array.pushMap(readableMapOf(value)) diff --git a/packages/react-native/ios/BreezSDKLiquidMapper.swift b/packages/react-native/ios/BreezSDKLiquidMapper.swift index 2523dd35b..d26da06da 100644 --- a/packages/react-native/ios/BreezSDKLiquidMapper.swift +++ b/packages/react-native/ios/BreezSDKLiquidMapper.swift @@ -108,6 +108,41 @@ enum BreezSDKLiquidMapper { return aesSuccessActionDataDecryptedList.map { v -> [String: Any?] in return dictionaryOf(aesSuccessActionDataDecrypted: v) } } + static func asAssetBalance(assetBalance: [String: Any?]) throws -> AssetBalance { + guard let assetId = assetBalance["assetId"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "assetId", typeName: "AssetBalance")) + } + guard let balance = assetBalance["balance"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "balance", typeName: "AssetBalance")) + } + + return AssetBalance(assetId: assetId, balance: balance) + } + + static func dictionaryOf(assetBalance: AssetBalance) -> [String: Any?] { + return [ + "assetId": assetBalance.assetId, + "balance": assetBalance.balance, + ] + } + + static func asAssetBalanceList(arr: [Any]) throws -> [AssetBalance] { + var list = [AssetBalance]() + for value in arr { + if let val = value as? [String: Any?] { + var assetBalance = try asAssetBalance(assetBalance: val) + list.append(assetBalance) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "AssetBalance")) + } + } + return list + } + + static func arrayOf(assetBalanceList: [AssetBalance]) -> [Any] { + return assetBalanceList.map { v -> [String: Any?] in return dictionaryOf(assetBalance: v) } + } + static func asBackupRequest(backupRequest: [String: Any?]) throws -> BackupRequest { var backupPath: String? if hasNonNilKey(data: backupRequest, key: "backupPath") { @@ -718,8 +753,12 @@ enum BreezSDKLiquidMapper { guard let pubkey = getInfoResponse["pubkey"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pubkey", typeName: "GetInfoResponse")) } + guard let assetBalancesTmp = getInfoResponse["assetBalances"] as? [[String: Any?]] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "assetBalances", typeName: "GetInfoResponse")) + } + let assetBalances = try asAssetBalanceList(arr: assetBalancesTmp) - return GetInfoResponse(balanceSat: balanceSat, pendingSendSat: pendingSendSat, pendingReceiveSat: pendingReceiveSat, fingerprint: fingerprint, pubkey: pubkey) + return GetInfoResponse(balanceSat: balanceSat, pendingSendSat: pendingSendSat, pendingReceiveSat: pendingReceiveSat, fingerprint: fingerprint, pubkey: pubkey, assetBalances: assetBalances) } static func dictionaryOf(getInfoResponse: GetInfoResponse) -> [String: Any?] { @@ -729,6 +768,7 @@ enum BreezSDKLiquidMapper { "pendingReceiveSat": getInfoResponse.pendingReceiveSat, "fingerprint": getInfoResponse.fingerprint, "pubkey": getInfoResponse.pubkey, + "assetBalances": arrayOf(assetBalanceList: getInfoResponse.assetBalances), ] } @@ -2217,14 +2257,22 @@ enum BreezSDKLiquidMapper { } payerAmountSat = payerAmountSatTmp } + var assetId: String? + if hasNonNilKey(data: prepareReceiveRequest, key: "assetId") { + guard let assetIdTmp = prepareReceiveRequest["assetId"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "assetId")) + } + assetId = assetIdTmp + } - return PrepareReceiveRequest(paymentMethod: paymentMethod, payerAmountSat: payerAmountSat) + return PrepareReceiveRequest(paymentMethod: paymentMethod, payerAmountSat: payerAmountSat, assetId: assetId) } static func dictionaryOf(prepareReceiveRequest: PrepareReceiveRequest) -> [String: Any?] { return [ "paymentMethod": valueOf(paymentMethod: prepareReceiveRequest.paymentMethod), "payerAmountSat": prepareReceiveRequest.payerAmountSat == nil ? nil : prepareReceiveRequest.payerAmountSat, + "assetId": prepareReceiveRequest.assetId == nil ? nil : prepareReceiveRequest.assetId, ] } @@ -2261,6 +2309,13 @@ enum BreezSDKLiquidMapper { } payerAmountSat = payerAmountSatTmp } + var assetId: String? + if hasNonNilKey(data: prepareReceiveResponse, key: "assetId") { + guard let assetIdTmp = prepareReceiveResponse["assetId"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "assetId")) + } + assetId = assetIdTmp + } var minPayerAmountSat: UInt64? if hasNonNilKey(data: prepareReceiveResponse, key: "minPayerAmountSat") { guard let minPayerAmountSatTmp = prepareReceiveResponse["minPayerAmountSat"] as? UInt64 else { @@ -2283,7 +2338,7 @@ enum BreezSDKLiquidMapper { swapperFeerate = swapperFeerateTmp } - return PrepareReceiveResponse(paymentMethod: paymentMethod, feesSat: feesSat, payerAmountSat: payerAmountSat, minPayerAmountSat: minPayerAmountSat, maxPayerAmountSat: maxPayerAmountSat, swapperFeerate: swapperFeerate) + return PrepareReceiveResponse(paymentMethod: paymentMethod, feesSat: feesSat, payerAmountSat: payerAmountSat, assetId: assetId, minPayerAmountSat: minPayerAmountSat, maxPayerAmountSat: maxPayerAmountSat, swapperFeerate: swapperFeerate) } static func dictionaryOf(prepareReceiveResponse: PrepareReceiveResponse) -> [String: Any?] { @@ -2291,6 +2346,7 @@ enum BreezSDKLiquidMapper { "paymentMethod": valueOf(paymentMethod: prepareReceiveResponse.paymentMethod), "feesSat": prepareReceiveResponse.feesSat, "payerAmountSat": prepareReceiveResponse.payerAmountSat == nil ? nil : prepareReceiveResponse.payerAmountSat, + "assetId": prepareReceiveResponse.assetId == nil ? nil : prepareReceiveResponse.assetId, "minPayerAmountSat": prepareReceiveResponse.minPayerAmountSat == nil ? nil : prepareReceiveResponse.minPayerAmountSat, "maxPayerAmountSat": prepareReceiveResponse.maxPayerAmountSat == nil ? nil : prepareReceiveResponse.maxPayerAmountSat, "swapperFeerate": prepareReceiveResponse.swapperFeerate == nil ? nil : prepareReceiveResponse.swapperFeerate, @@ -2405,13 +2461,22 @@ enum BreezSDKLiquidMapper { amount = try asPayAmount(payAmount: amountTmp) } - return PrepareSendRequest(destination: destination, amount: amount) + var assetId: String? + if hasNonNilKey(data: prepareSendRequest, key: "assetId") { + guard let assetIdTmp = prepareSendRequest["assetId"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "assetId")) + } + assetId = assetIdTmp + } + + return PrepareSendRequest(destination: destination, amount: amount, assetId: assetId) } static func dictionaryOf(prepareSendRequest: PrepareSendRequest) -> [String: Any?] { return [ "destination": prepareSendRequest.destination, "amount": prepareSendRequest.amount == nil ? nil : dictionaryOf(payAmount: prepareSendRequest.amount!), + "assetId": prepareSendRequest.assetId == nil ? nil : prepareSendRequest.assetId, ] } @@ -3509,10 +3574,11 @@ enum BreezSDKLiquidMapper { static func asListPaymentDetails(listPaymentDetails: [String: Any?]) throws -> ListPaymentDetails { let type = listPaymentDetails["type"] as! String if type == "liquid" { - guard let _destination = listPaymentDetails["destination"] as? String else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "destination", typeName: "ListPaymentDetails")) - } - return ListPaymentDetails.liquid(destination: _destination) + let _assetId = listPaymentDetails["assetId"] as? String + + let _destination = listPaymentDetails["destination"] as? String + + return ListPaymentDetails.liquid(assetId: _assetId, destination: _destination) } if type == "bitcoin" { guard let _address = listPaymentDetails["address"] as? String else { @@ -3527,11 +3593,12 @@ enum BreezSDKLiquidMapper { static func dictionaryOf(listPaymentDetails: ListPaymentDetails) -> [String: Any?] { switch listPaymentDetails { case let .liquid( - destination + assetId, destination ): return [ "type": "liquid", - "destination": destination, + "assetId": assetId == nil ? nil : assetId, + "destination": destination == nil ? nil : destination, ] case let .bitcoin( @@ -3891,13 +3958,16 @@ enum BreezSDKLiquidMapper { return PaymentDetails.lightning(swapId: _swapId, description: _description, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, lnurlInfo: _lnurlInfo, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) } if type == "liquid" { + guard let _assetId = paymentDetails["assetId"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "assetId", typeName: "PaymentDetails")) + } guard let _destination = paymentDetails["destination"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "destination", typeName: "PaymentDetails")) } guard let _description = paymentDetails["description"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "description", typeName: "PaymentDetails")) } - return PaymentDetails.liquid(destination: _destination, description: _description) + return PaymentDetails.liquid(assetId: _assetId, destination: _destination, description: _description) } if type == "bitcoin" { guard let _swapId = paymentDetails["swapId"] as? String else { @@ -3935,10 +4005,11 @@ enum BreezSDKLiquidMapper { ] case let .liquid( - destination, description + assetId, destination, description ): return [ "type": "liquid", + "assetId": assetId, "destination": destination, "description": description, ] diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index f17447e9f..7f9f55507 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -34,6 +34,11 @@ export interface AesSuccessActionDataDecrypted { plaintext: string } +export interface AssetBalance { + assetId: string + balance: number +} + export interface BackupRequest { backupPath?: string } @@ -125,6 +130,7 @@ export interface GetInfoResponse { pendingReceiveSat: number fingerprint: string pubkey: string + assetBalances: AssetBalance[] } export interface LnInvoice { @@ -335,12 +341,14 @@ export interface PreparePayOnchainResponse { export interface PrepareReceiveRequest { paymentMethod: PaymentMethod payerAmountSat?: number + assetId?: string } export interface PrepareReceiveResponse { paymentMethod: PaymentMethod feesSat: number payerAmountSat?: number + assetId?: string minPayerAmountSat?: number maxPayerAmountSat?: number swapperFeerate?: number @@ -361,6 +369,7 @@ export interface PrepareRefundResponse { export interface PrepareSendRequest { destination: string amount?: PayAmount + assetId?: string } export interface PrepareSendResponse { @@ -551,7 +560,8 @@ export enum ListPaymentDetailsVariant { export type ListPaymentDetails = { type: ListPaymentDetailsVariant.LIQUID, - destination: string + assetId?: string + destination?: string } | { type: ListPaymentDetailsVariant.BITCOIN, address: string @@ -641,6 +651,7 @@ export type PaymentDetails = { refundTxAmountSat?: number } | { type: PaymentDetailsVariant.LIQUID, + assetId: string destination: string description: string } | {