From 4dfef3b132ad727466833b8399b6bee228ac4d69 Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Fri, 17 Jan 2025 20:54:02 +0100 Subject: [PATCH] Multi-asset send/receive --- cli/src/commands.rs | 80 +++- .../job/LnurlPayInvoice.kt | 3 +- .../include/breez_sdk_liquid.h | 56 ++- .../BreezSDKLiquid/Task/LnurlPayInvoice.swift | 3 +- lib/bindings/src/breez_sdk_liquid.udl | 25 +- lib/core/src/chain_swap.rs | 10 +- lib/core/src/frb_generated.rs | 441 ++++++++++++++++-- lib/core/src/model.rs | 110 ++++- lib/core/src/persist/migrations.rs | 12 +- lib/core/src/persist/mod.rs | 226 +++++---- lib/core/src/receive_swap.rs | 3 +- lib/core/src/sdk.rs | 320 ++++++++++--- lib/core/src/send_swap.rs | 4 +- lib/core/src/test_utils/persist.rs | 10 +- lib/core/src/test_utils/wallet.rs | 2 + lib/core/src/wallet.rs | 43 +- packages/dart/lib/src/frb_generated.dart | 232 +++++++-- packages/dart/lib/src/frb_generated.io.dart | 217 ++++++++- packages/dart/lib/src/model.dart | 98 +++- packages/dart/lib/src/model.freezed.dart | 419 ++++++++++++++--- ...utter_breez_liquid_bindings_generated.dart | 91 +++- .../breezsdkliquid/BreezSDKLiquidMapper.kt | 147 ++++-- packages/react-native/example/App.js | 5 +- .../ios/BreezSDKLiquidMapper.swift | 190 ++++++-- packages/react-native/src/index.ts | 41 +- 25 files changed, 2315 insertions(+), 473 deletions(-) diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 95eb9032e..eda6151f0 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -33,10 +33,16 @@ pub(crate) enum Command { #[arg(long)] address: Option, - /// The amount in satoshi to pay, in case of a direct Liquid address - /// or amount-less BIP21 + /// The amount to pay, in case of a direct Liquid address or amount-less BIP21. + /// If an asset id is provided, it is the base unit of that asset depending on its + /// precision, otherwise in satoshi. #[arg(short, long)] - amount_sat: Option, + receiver_amount: 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)] @@ -72,10 +78,15 @@ pub(crate) enum Command { #[arg(short = 'm', long = "method")] payment_method: Option, - /// Amount the payer will send, in satoshi - /// If not specified, it will generate a BIP21 URI/Liquid address with no amount + /// The amount the payer should send. If an asset id is provided, it is the base + /// unit of that asset depending on its precision, otherwise in satoshi. + /// If not specified, it will generate a BIP21 URI/address with no amount. #[arg(short, long)] - payer_amount_sat: Option, + payer_amount: 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")] @@ -118,6 +129,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, @@ -266,19 +281,29 @@ pub(crate) async fn handle_command( Ok(match command { Command::ReceivePayment { payment_method, - payer_amount_sat, + payer_amount, + asset_id, description, use_description_hash, } => { + let amount = match asset_id { + Some(asset_id) => Some(ReceiveAmount::Asset { + asset_id, + payer_amount, + }), + None => { + payer_amount.map(|payer_amount_sat| ReceiveAmount::Bitcoin { payer_amount_sat }) + } + }; let prepare_response = sdk .prepare_receive_payment(&PrepareReceiveRequest { - payer_amount_sat, payment_method: payment_method.unwrap_or(PaymentMethod::Lightning), + amount: amount.clone(), }) .await?; let fees = prepare_response.fees_sat; - let confirmation_msg = match payer_amount_sat { + let confirmation_msg = match amount { Some(_) => format!("Fees: {fees} sat. Are the fees acceptable? (y/N)"), None => { let min = prepare_response.min_payer_amount_sat; @@ -332,13 +357,14 @@ pub(crate) async fn handle_command( invoice, offer, address, - amount_sat, + receiver_amount, + asset_id, drain, delay, } => { let destination = match (invoice, offer, address) { (Some(invoice), None, None) => Ok(invoice), - (None, Some(offer), None) => match amount_sat { + (None, Some(offer), None) => match receiver_amount { Some(_) => Ok(offer), None => Err(anyhow!( "Must specify an amount for a BOLT12 offer." @@ -354,10 +380,16 @@ pub(crate) async fn handle_command( "Must specify either a BOLT11 invoice, a BOLT12 offer or a direct/BIP21 address." )) }?; - let amount = match (amount_sat, drain.unwrap_or(false)) { - (Some(amount_sat), _) => Some(PayAmount::Receiver { amount_sat }), - (_, true) => Some(PayAmount::Drain), - (_, _) => None, + let amount = match (asset_id, receiver_amount, drain.unwrap_or(false)) { + (Some(asset_id), Some(receiver_amount), _) => Some(PayAmount::Asset { + asset_id, + receiver_amount, + }), + (None, Some(receiver_amount_sat), _) => Some(PayAmount::Bitcoin { + receiver_amount_sat, + }), + (_, _, true) => Some(PayAmount::Drain), + _ => None, }; let prepare_response = sdk @@ -400,8 +432,8 @@ pub(crate) async fn handle_command( } => { let amount = match drain.unwrap_or(false) { true => PayAmount::Drain, - false => PayAmount::Receiver { - amount_sat: receiver_amount_sat.ok_or(anyhow::anyhow!( + false => PayAmount::Bitcoin { + receiver_amount_sat: receiver_amount_sat.ok_or(anyhow::anyhow!( "Must specify `receiver_amount_sat` if not draining" ))?, }, @@ -488,12 +520,20 @@ 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: Some(address), + }), _ => None, }; diff --git a/lib/bindings/langs/android/lib/src/main/kotlin/breez_sdk_liquid_notification/job/LnurlPayInvoice.kt b/lib/bindings/langs/android/lib/src/main/kotlin/breez_sdk_liquid_notification/job/LnurlPayInvoice.kt index 823267cae..2ab682af4 100644 --- a/lib/bindings/langs/android/lib/src/main/kotlin/breez_sdk_liquid_notification/job/LnurlPayInvoice.kt +++ b/lib/bindings/langs/android/lib/src/main/kotlin/breez_sdk_liquid_notification/job/LnurlPayInvoice.kt @@ -4,6 +4,7 @@ import android.content.Context import breez_sdk_liquid.BindingLiquidSdk import breez_sdk_liquid.PaymentMethod import breez_sdk_liquid.PrepareReceiveRequest +import breez_sdk_liquid.ReceiveAmount import breez_sdk_liquid.ReceivePaymentRequest import breez_sdk_liquid_notification.Constants.DEFAULT_LNURL_PAY_INVOICE_NOTIFICATION_TITLE import breez_sdk_liquid_notification.Constants.DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT @@ -62,7 +63,7 @@ class LnurlPayInvoiceJob( DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT ) val prepareReceivePaymentRes = liquidSDK.prepareReceivePayment( - PrepareReceiveRequest(PaymentMethod.LIGHTNING, amountSat) + PrepareReceiveRequest(PaymentMethod.LIGHTNING, ReceiveAmount.Bitcoin(amountSat)) ) val receivePaymentResponse = liquidSDK.receivePayment( ReceivePaymentRequest( 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 5b163dffa..e3f3aef4e 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 @@ -102,6 +102,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; @@ -352,12 +353,18 @@ typedef struct wire_cst_prepare_ln_url_pay_request { bool *validate_success_action_url; } wire_cst_prepare_ln_url_pay_request; -typedef struct wire_cst_PayAmount_Receiver { - uint64_t amount_sat; -} wire_cst_PayAmount_Receiver; +typedef struct wire_cst_PayAmount_Bitcoin { + uint64_t receiver_amount_sat; +} wire_cst_PayAmount_Bitcoin; + +typedef struct wire_cst_PayAmount_Asset { + struct wire_cst_list_prim_u_8_strict *asset_id; + uint64_t receiver_amount; +} wire_cst_PayAmount_Asset; typedef union PayAmountKind { - struct wire_cst_PayAmount_Receiver Receiver; + struct wire_cst_PayAmount_Bitcoin Bitcoin; + struct wire_cst_PayAmount_Asset Asset; } PayAmountKind; typedef struct wire_cst_pay_amount { @@ -370,9 +377,28 @@ typedef struct wire_cst_prepare_pay_onchain_request { uint32_t *fee_rate_sat_per_vbyte; } wire_cst_prepare_pay_onchain_request; +typedef struct wire_cst_ReceiveAmount_Bitcoin { + uint64_t payer_amount_sat; +} wire_cst_ReceiveAmount_Bitcoin; + +typedef struct wire_cst_ReceiveAmount_Asset { + struct wire_cst_list_prim_u_8_strict *asset_id; + uint64_t *payer_amount; +} wire_cst_ReceiveAmount_Asset; + +typedef union ReceiveAmountKind { + struct wire_cst_ReceiveAmount_Bitcoin Bitcoin; + struct wire_cst_ReceiveAmount_Asset Asset; +} ReceiveAmountKind; + +typedef struct wire_cst_receive_amount { + int32_t tag; + union ReceiveAmountKind kind; +} wire_cst_receive_amount; + typedef struct wire_cst_prepare_receive_request { - uint64_t *payer_amount_sat; int32_t payment_method; + struct wire_cst_receive_amount *amount; } wire_cst_prepare_receive_request; typedef struct wire_cst_prepare_refund_request { @@ -388,7 +414,7 @@ typedef struct wire_cst_prepare_send_request { typedef struct wire_cst_prepare_receive_response { int32_t payment_method; - uint64_t *payer_amount_sat; + struct wire_cst_receive_amount *amount; uint64_t fees_sat; uint64_t *min_payer_amount_sat; uint64_t *max_payer_amount_sat; @@ -501,6 +527,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 { @@ -645,6 +672,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; @@ -727,6 +764,7 @@ typedef struct wire_cst_wallet_info { 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_wallet_info; typedef struct wire_cst_get_info_response { @@ -1335,6 +1373,8 @@ struct wire_cst_prepare_refund_request *frbgen_breez_liquid_cst_new_box_autoadd_ struct wire_cst_prepare_send_request *frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request(void); +struct wire_cst_receive_amount *frbgen_breez_liquid_cst_new_box_autoadd_receive_amount(void); + struct wire_cst_receive_payment_request *frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request(void); struct wire_cst_refund_request *frbgen_breez_liquid_cst_new_box_autoadd_refund_request(void); @@ -1361,6 +1401,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); @@ -1429,6 +1471,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_refund_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request); + dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_receive_amount); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_refund_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_restore_request); @@ -1442,6 +1485,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/langs/swift/Sources/BreezSDKLiquid/Task/LnurlPayInvoice.swift b/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/LnurlPayInvoice.swift index c0050998b..2143d9537 100644 --- a/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/LnurlPayInvoice.swift +++ b/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/LnurlPayInvoice.swift @@ -45,7 +45,8 @@ class LnurlPayInvoiceTask : LnurlPayTask { } let plainTextMetadata = ResourceHelper.shared.getString(key: Constants.LNURL_PAY_METADATA_PLAIN_TEXT, fallback: Constants.DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT) let metadata = "[[\"text/plain\",\"\(plainTextMetadata)\"]]" - let prepareReceivePaymentRes = try liquidSDK.prepareReceivePayment(req: PrepareReceiveRequest(paymentMethod: PaymentMethod.lightning, payerAmountSat: amountSat)) + let amount = ReceiveAmount.bitcoin(payerAmountSat: amountSat) + let prepareReceivePaymentRes = try liquidSDK.prepareReceivePayment(req: PrepareReceiveRequest(paymentMethod: PaymentMethod.lightning, amount: amount)) let receivePaymentRes = try liquidSDK.receivePayment(req: ReceivePaymentRequest(prepareResponse: prepareReceivePaymentRes, description: metadata, useDescriptionHash: true)) self.replyServer(encodable: LnurlInvoiceResponse(pr: receivePaymentRes.destination, routes: []), replyURL: request!.reply_url) } catch let e { diff --git a/lib/bindings/src/breez_sdk_liquid.udl b/lib/bindings/src/breez_sdk_liquid.udl index a22a86386..3c8f867f9 100644 --- a/lib/bindings/src/breez_sdk_liquid.udl +++ b/lib/bindings/src/breez_sdk_liquid.udl @@ -355,6 +355,11 @@ dictionary ConnectWithSignerRequest { Config config; }; +dictionary AssetBalance { + string asset_id; + u64 balance; +}; + dictionary BlockchainInfo { u32 liquid_tip; u32 bitcoin_tip; @@ -366,6 +371,7 @@ dictionary WalletInfo { u64 pending_receive_sat; string fingerprint; string pubkey; + sequence asset_balances; }; dictionary GetInfoResponse { @@ -441,15 +447,21 @@ enum PaymentMethod { "LiquidAddress", }; +[Enum] +interface ReceiveAmount { + Bitcoin(u64 payer_amount_sat); + Asset(string asset_id, u64? payer_amount); +}; + dictionary PrepareReceiveRequest { PaymentMethod payment_method; - u64? payer_amount_sat = null; + ReceiveAmount? amount = null; }; dictionary PrepareReceiveResponse { PaymentMethod payment_method; u64 fees_sat; - u64? payer_amount_sat; + ReceiveAmount? amount; u64? min_payer_amount_sat; u64? max_payer_amount_sat; f64? swapper_feerate; @@ -483,7 +495,8 @@ dictionary OnchainPaymentLimitsResponse { [Enum] interface PayAmount { - Receiver(u64 amount_sat); + Bitcoin(u64 receiver_amount_sat); + Asset(string asset_id, u64 receiver_amount); Drain(); }; @@ -543,8 +556,8 @@ dictionary ListPaymentsRequest { [Enum] interface ListPaymentDetails { - Liquid(string destination); - Bitcoin(string address); + Liquid(string? asset_id, string? destination); + Bitcoin(string? address); }; [Enum] @@ -580,7 +593,7 @@ dictionary LnUrlInfo { [Enum] interface PaymentDetails { Lightning(string swap_id, string description, u32 liquid_expiration_blockheight, string? preimage, string? invoice, string? bolt12_offer, string? payment_hash, string? destination_pubkey, 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, u32? bitcoin_expiration_blockheight, u32? liquid_expiration_blockheight, 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 39b073142..7f1b5aaf9 100644 --- a/lib/core/src/chain_swap.rs +++ b/lib/core/src/chain_swap.rs @@ -527,9 +527,9 @@ impl ChainSwapHandler { self.persister.insert_or_update_payment(PaymentTxData { tx_id: lockup_tx_id.clone(), timestamp: Some(utils::now()), - 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, + asset_id: self.config.lbtc_asset_id().to_string(), + amount: create_response.lockup_details.amount, + fees_sat: lockup_tx_fees_sat, payment_type: PaymentType::Send, is_confirmed: false, unblinding_data: None, @@ -737,6 +737,7 @@ impl ChainSwapHandler { .build_tx_or_drain_tx( Some(LIQUID_FEE_RATE_MSAT_PER_VBYTE), &lockup_details.lockup_address, + &self.config.lbtc_asset_id().to_string(), lockup_details.amount, ) .await?; @@ -861,7 +862,8 @@ impl ChainSwapHandler { PaymentTxData { tx_id: claim_tx_id.clone(), timestamp: Some(utils::now()), - amount_sat: swap.receiver_amount_sat, + asset_id: self.config.lbtc_asset_id().to_string(), + amount: swap.receiver_amount_sat, fees_sat: 0, payment_type: PaymentType::Receive, is_confirmed: false, diff --git a/lib/core/src/frb_generated.rs b/lib/core/src/frb_generated.rs index 2610446d7..9ee80052c 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 { @@ -2794,6 +2806,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 { @@ -2876,13 +2900,15 @@ 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, }; } 1 => { - let mut var_address = ::sse_decode(deserializer); + let mut var_address = >::sse_decode(deserializer); return crate::model::ListPaymentDetails::Bitcoin { address: var_address, }; @@ -3605,6 +3631,17 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -3703,12 +3740,20 @@ impl SseDecode for crate::model::PayAmount { let mut tag_ = ::sse_decode(deserializer); match tag_ { 0 => { - let mut var_amountSat = ::sse_decode(deserializer); - return crate::model::PayAmount::Receiver { - amount_sat: var_amountSat, + let mut var_receiverAmountSat = ::sse_decode(deserializer); + return crate::model::PayAmount::Bitcoin { + receiver_amount_sat: var_receiverAmountSat, }; } 1 => { + let mut var_assetId = ::sse_decode(deserializer); + let mut var_receiverAmount = ::sse_decode(deserializer); + return crate::model::PayAmount::Asset { + asset_id: var_assetId, + receiver_amount: var_receiverAmount, + }; + } + 2 => { return crate::model::PayAmount::Drain; } _ => { @@ -3793,9 +3838,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 => { @@ -4043,11 +4090,11 @@ 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_amount = >::sse_decode(deserializer); return crate::model::PrepareReceiveRequest { - payer_amount_sat: var_payerAmountSat, payment_method: var_paymentMethod, + amount: var_amount, }; } } @@ -4056,14 +4103,14 @@ impl SseDecode for crate::model::PrepareReceiveResponse { // 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_paymentMethod = ::sse_decode(deserializer); - let mut var_payerAmountSat = >::sse_decode(deserializer); + let mut var_amount = >::sse_decode(deserializer); let mut var_feesSat = ::sse_decode(deserializer); let mut var_minPayerAmountSat = >::sse_decode(deserializer); let mut var_maxPayerAmountSat = >::sse_decode(deserializer); let mut var_swapperFeerate = >::sse_decode(deserializer); return crate::model::PrepareReceiveResponse { payment_method: var_paymentMethod, - payer_amount_sat: var_payerAmountSat, + amount: var_amount, fees_sat: var_feesSat, min_payer_amount_sat: var_minPayerAmountSat, max_payer_amount_sat: var_maxPayerAmountSat, @@ -4136,6 +4183,32 @@ impl SseDecode for crate::bindings::Rate { } } +impl SseDecode for crate::model::ReceiveAmount { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_payerAmountSat = ::sse_decode(deserializer); + return crate::model::ReceiveAmount::Bitcoin { + payer_amount_sat: var_payerAmountSat, + }; + } + 1 => { + let mut var_assetId = ::sse_decode(deserializer); + let mut var_payerAmount = >::sse_decode(deserializer); + return crate::model::ReceiveAmount::Asset { + asset_id: var_assetId, + payer_amount: var_payerAmount, + }; + } + _ => { + unimplemented!(""); + } + } + } +} + impl SseDecode for crate::model::ReceivePaymentRequest { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -4545,12 +4618,14 @@ impl SseDecode for crate::model::WalletInfo { 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::WalletInfo { balance_sat: var_balanceSat, pending_send_sat: var_pendingSendSat, pending_receive_sat: var_pendingReceiveSat, fingerprint: var_fingerprint, pubkey: var_pubkey, + asset_balances: var_assetBalances, }; } } @@ -4718,6 +4793,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() @@ -5209,9 +5300,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() } @@ -5876,10 +5973,23 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { match self { - crate::model::PayAmount::Receiver { amount_sat } => { - [0.into_dart(), amount_sat.into_into_dart().into_dart()].into_dart() - } - crate::model::PayAmount::Drain => [1.into_dart()].into_dart(), + crate::model::PayAmount::Bitcoin { + receiver_amount_sat, + } => [ + 0.into_dart(), + receiver_amount_sat.into_into_dart().into_dart(), + ] + .into_dart(), + crate::model::PayAmount::Asset { + asset_id, + receiver_amount, + } => [ + 1.into_dart(), + asset_id.into_into_dart().into_dart(), + receiver_amount.into_into_dart().into_dart(), + ] + .into_dart(), + crate::model::PayAmount::Drain => [2.into_dart()].into_dart(), _ => { unimplemented!(""); } @@ -5971,10 +6081,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 { @@ -6264,8 +6376,8 @@ 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.amount.into_into_dart().into_dart(), ] .into_dart() } @@ -6286,7 +6398,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveResponse { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ self.payment_method.into_into_dart().into_dart(), - self.payer_amount_sat.into_into_dart().into_dart(), + self.amount.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(), @@ -6414,6 +6526,36 @@ impl flutter_rust_bridge::IntoIntoDart> } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::model::ReceiveAmount { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::model::ReceiveAmount::Bitcoin { payer_amount_sat } => { + [0.into_dart(), payer_amount_sat.into_into_dart().into_dart()].into_dart() + } + crate::model::ReceiveAmount::Asset { + asset_id, + payer_amount, + } => [ + 1.into_dart(), + asset_id.into_into_dart().into_dart(), + payer_amount.into_into_dart().into_dart(), + ] + .into_dart(), + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::ReceiveAmount {} +impl flutter_rust_bridge::IntoIntoDart + for crate::model::ReceiveAmount +{ + fn into_into_dart(self) -> crate::model::ReceiveAmount { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::model::ReceivePaymentRequest { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -6862,6 +7004,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::WalletInfo { 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() } @@ -6989,6 +7132,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) { @@ -7296,6 +7447,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) { @@ -7360,13 +7521,17 @@ 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); - ::sse_encode(address, serializer); + >::sse_encode(address, serializer); } _ => { unimplemented!(""); @@ -7933,6 +8098,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // 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.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -8017,12 +8192,22 @@ impl SseEncode for crate::model::PayAmount { // 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::PayAmount::Receiver { amount_sat } => { + crate::model::PayAmount::Bitcoin { + receiver_amount_sat, + } => { ::sse_encode(0, serializer); - ::sse_encode(amount_sat, serializer); + ::sse_encode(receiver_amount_sat, serializer); } - crate::model::PayAmount::Drain => { + crate::model::PayAmount::Asset { + asset_id, + receiver_amount, + } => { ::sse_encode(1, serializer); + ::sse_encode(asset_id, serializer); + ::sse_encode(receiver_amount, serializer); + } + crate::model::PayAmount::Drain => { + ::sse_encode(2, serializer); } _ => { unimplemented!(""); @@ -8088,10 +8273,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, @@ -8314,8 +8501,8 @@ 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.amount, serializer); } } @@ -8323,7 +8510,7 @@ impl SseEncode for crate::model::PrepareReceiveResponse { // 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.payment_method, serializer); - >::sse_encode(self.payer_amount_sat, serializer); + >::sse_encode(self.amount, serializer); ::sse_encode(self.fees_sat, serializer); >::sse_encode(self.min_payer_amount_sat, serializer); >::sse_encode(self.max_payer_amount_sat, serializer); @@ -8373,6 +8560,29 @@ impl SseEncode for crate::bindings::Rate { } } +impl SseEncode for crate::model::ReceiveAmount { + // 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::ReceiveAmount::Bitcoin { payer_amount_sat } => { + ::sse_encode(0, serializer); + ::sse_encode(payer_amount_sat, serializer); + } + crate::model::ReceiveAmount::Asset { + asset_id, + payer_amount, + } => { + ::sse_encode(1, serializer); + ::sse_encode(asset_id, serializer); + >::sse_encode(payer_amount, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + impl SseEncode for crate::model::ReceivePaymentRequest { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -8690,6 +8900,7 @@ impl SseEncode for crate::model::WalletInfo { ::sse_encode(self.pending_receive_sat, serializer); ::sse_encode(self.fingerprint, serializer); ::sse_encode(self.pubkey, serializer); + >::sse_encode(self.asset_balances, serializer); } } @@ -8855,6 +9066,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 { @@ -9195,6 +9415,13 @@ mod io { CstDecode::::cst_decode(*wrap).into() } } + impl CstDecode for *mut wire_cst_receive_amount { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::ReceiveAmount { + let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }; + CstDecode::::cst_decode(*wrap).into() + } + } impl CstDecode for *mut wire_cst_receive_payment_request { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::ReceivePaymentRequest { @@ -9529,6 +9756,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 { @@ -9600,6 +9837,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(), } } @@ -10117,12 +10355,19 @@ mod io { fn cst_decode(self) -> crate::model::PayAmount { match self.tag { 0 => { - let ans = unsafe { self.kind.Receiver }; - crate::model::PayAmount::Receiver { - amount_sat: ans.amount_sat.cst_decode(), + let ans = unsafe { self.kind.Bitcoin }; + crate::model::PayAmount::Bitcoin { + receiver_amount_sat: ans.receiver_amount_sat.cst_decode(), + } + } + 1 => { + let ans = unsafe { self.kind.Asset }; + crate::model::PayAmount::Asset { + asset_id: ans.asset_id.cst_decode(), + receiver_amount: ans.receiver_amount.cst_decode(), } } - 1 => crate::model::PayAmount::Drain, + 2 => crate::model::PayAmount::Drain, _ => unreachable!(), } } @@ -10180,6 +10425,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 => { @@ -10346,8 +10592,8 @@ 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(), + amount: self.amount.cst_decode(), } } } @@ -10356,7 +10602,7 @@ mod io { fn cst_decode(self) -> crate::model::PrepareReceiveResponse { crate::model::PrepareReceiveResponse { payment_method: self.payment_method.cst_decode(), - payer_amount_sat: self.payer_amount_sat.cst_decode(), + amount: self.amount.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(), @@ -10411,6 +10657,27 @@ mod io { } } } + impl CstDecode for wire_cst_receive_amount { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::ReceiveAmount { + match self.tag { + 0 => { + let ans = unsafe { self.kind.Bitcoin }; + crate::model::ReceiveAmount::Bitcoin { + payer_amount_sat: ans.payer_amount_sat.cst_decode(), + } + } + 1 => { + let ans = unsafe { self.kind.Asset }; + crate::model::ReceiveAmount::Asset { + asset_id: ans.asset_id.cst_decode(), + payer_amount: ans.payer_amount.cst_decode(), + } + } + _ => unreachable!(), + } + } + } impl CstDecode for wire_cst_receive_payment_request { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::ReceivePaymentRequest { @@ -10713,6 +10980,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(), } } } @@ -10781,6 +11049,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 { @@ -11600,8 +11881,8 @@ 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(), + amount: core::ptr::null_mut(), } } } @@ -11614,7 +11895,7 @@ mod io { fn new_with_null_ptr() -> Self { Self { payment_method: Default::default(), - payer_amount_sat: core::ptr::null_mut(), + amount: core::ptr::null_mut(), fees_sat: Default::default(), min_payer_amount_sat: core::ptr::null_mut(), max_payer_amount_sat: core::ptr::null_mut(), @@ -11694,6 +11975,19 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_receive_amount { + fn new_with_null_ptr() -> Self { + Self { + tag: -1, + kind: ReceiveAmountKind { nil__: () }, + } + } + } + impl Default for wire_cst_receive_amount { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_receive_payment_request { fn new_with_null_ptr() -> Self { Self { @@ -11968,6 +12262,7 @@ mod io { pending_receive_sat: Default::default(), fingerprint: core::ptr::null_mut(), pubkey: core::ptr::null_mut(), + asset_balances: core::ptr::null_mut(), } } } @@ -12661,6 +12956,14 @@ mod io { ) } + #[no_mangle] + pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_receive_amount( + ) -> *mut wire_cst_receive_amount { + flutter_rust_bridge::for_generated::new_leak_box_ptr( + wire_cst_receive_amount::new_with_null_ptr(), + ) + } + #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request( ) -> *mut wire_cst_receive_payment_request { @@ -12760,6 +13063,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, @@ -12998,6 +13315,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, } @@ -13225,6 +13548,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, @@ -13275,6 +13604,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)] @@ -13710,13 +14040,20 @@ mod io { #[repr(C)] #[derive(Clone, Copy)] pub union PayAmountKind { - Receiver: wire_cst_PayAmount_Receiver, + Bitcoin: wire_cst_PayAmount_Bitcoin, + Asset: wire_cst_PayAmount_Asset, nil__: (), } #[repr(C)] #[derive(Clone, Copy)] - pub struct wire_cst_PayAmount_Receiver { - amount_sat: u64, + pub struct wire_cst_PayAmount_Bitcoin { + receiver_amount_sat: u64, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_PayAmount_Asset { + asset_id: *mut wire_cst_list_prim_u_8_strict, + receiver_amount: u64, } #[repr(C)] #[derive(Clone, Copy)] @@ -13772,6 +14109,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)] @@ -13901,14 +14239,14 @@ mod io { #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_prepare_receive_request { - payer_amount_sat: *mut u64, payment_method: i32, + amount: *mut wire_cst_receive_amount, } #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_prepare_receive_response { payment_method: i32, - payer_amount_sat: *mut u64, + amount: *mut wire_cst_receive_amount, fees_sat: u64, min_payer_amount_sat: *mut u64, max_payer_amount_sat: *mut u64, @@ -13948,6 +14286,30 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_receive_amount { + tag: i32, + kind: ReceiveAmountKind, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub union ReceiveAmountKind { + Bitcoin: wire_cst_ReceiveAmount_Bitcoin, + Asset: wire_cst_ReceiveAmount_Asset, + nil__: (), + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_ReceiveAmount_Bitcoin { + payer_amount_sat: u64, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_ReceiveAmount_Asset { + asset_id: *mut wire_cst_list_prim_u_8_strict, + payer_amount: *mut u64, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_receive_payment_request { prepare_response: wire_cst_prepare_receive_response, description: *mut wire_cst_list_prim_u_8_strict, @@ -14214,6 +14576,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, } } #[cfg(not(target_family = "wasm"))] diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index be9057247..f23fa4545 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -135,6 +135,10 @@ impl Config { .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT) } + pub(crate) fn lbtc_asset_id(&self) -> String { + utils::lbtc_asset_id(self.network).to_string() + } + pub(crate) fn get_all_external_input_parsers(&self) -> Vec { let mut external_input_parsers = Vec::new(); if self.use_default_external_input_parsers { @@ -335,18 +339,32 @@ pub enum PaymentMethod { LiquidAddress, } +#[derive(Debug, Serialize, Clone)] +pub enum ReceiveAmount { + /// The amount in satoshi that should be paid + Bitcoin { payer_amount_sat: u64 }, + + /// The amount of an asset that should be paid + Asset { + asset_id: String, + payer_amount: Option, + }, +} + /// 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 amount to be paid in either Bitcoin or another asset + pub amount: Option, } /// Returned when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. #[derive(Debug, Serialize)] pub struct PrepareReceiveResponse { pub payment_method: PaymentMethod, - pub payer_amount_sat: Option, + pub amount: Option, /// Generally represents the total fees that would be paid to send or receive this payment. /// @@ -467,8 +485,15 @@ pub struct SendPaymentResponse { #[derive(Debug, Serialize, Clone)] pub enum PayAmount { /// The amount in satoshi that will be received - Receiver { amount_sat: u64 }, - /// Indicates that all available funds should be sent + Bitcoin { receiver_amount_sat: u64 }, + + /// The amount of an asset that will be received + Asset { + asset_id: String, + receiver_amount: u64, + }, + + /// Indicates that all available Bitcoin funds should be sent Drain, } @@ -531,6 +556,13 @@ pub struct RefundResponse { pub refund_tx_id: String, } +/// An asset balance to denote the balance for each asset. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AssetBalance { + pub asset_id: String, + pub balance: u64, +} + #[derive(Debug, Serialize, Deserialize, Default)] pub struct BlockchainInfo { pub liquid_tip: u32, @@ -549,6 +581,39 @@ pub struct WalletInfo { pub fingerprint: String, /// The wallet's pubkey. Used to verify signed messages. pub pubkey: String, + /// Asset balances of non Liquid Bitcoin assets + #[serde(default)] + pub asset_balances: Vec, +} + +impl WalletInfo { + pub(crate) fn validate_sufficient_funds( + &self, + network: LiquidNetwork, + amount_sat: u64, + fees_sat: u64, + asset_id: &str, + ) -> Result<(), PaymentError> { + if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) { + 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(()) + } } /// Returned when calling [crate::sdk::LiquidSdk::get_info]. @@ -625,11 +690,19 @@ pub struct ListPaymentsRequest { /// An argument of [ListPaymentsRequest] when calling [crate::sdk::LiquidSdk::list_payments]. #[derive(Debug, Serialize)] pub enum ListPaymentDetails { - /// The Liquid BIP21 URI or address of the payment - Liquid { destination: String }, + /// A Liquid payment + Liquid { + /// Optional asset id + asset_id: Option, + /// Optional BIP21 URI or address + destination: Option, + }, - /// The Bitcoin address of the payment - Bitcoin { address: String }, + /// A Bitcoin payment + Bitcoin { + /// Optional address + address: Option, + }, } /// An argument when calling [crate::sdk::LiquidSdk::get_payment]. @@ -1297,10 +1370,13 @@ 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. - pub amount_sat: u64, + pub amount: u64, /// The onchain fees of this tx pub fees_sat: u64, @@ -1420,6 +1496,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 { @@ -1467,6 +1546,15 @@ impl PaymentDetails { Self::Liquid { .. } => None, } } + + pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool { + match self { + Self::Liquid { asset_id, .. } => { + asset_id.eq(&utils::lbtc_asset_id(network).to_string()) + } + _ => true, + } + } } /// Represents an SDK payment. @@ -1593,13 +1681,13 @@ impl Payment { .timestamp .or(swap.as_ref().map(|s| s.created_at)) .unwrap_or(utils::now()), - amount_sat: tx.amount_sat, + amount_sat: tx.amount, fees_sat: match swap.as_ref() { Some(s) => match tx.payment_type { // For receive swaps, to avoid some edge case issues related to potential past // overpayments, we use the actual claim value as the final received amount // for fee calculation. - PaymentType::Receive => s.payer_amount_sat.saturating_sub(tx.amount_sat), + PaymentType::Receive => s.payer_amount_sat.saturating_sub(tx.amount), PaymentType::Send => s.payer_amount_sat.saturating_sub(s.receiver_amount_sat), }, None => match tx.payment_type { diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index b5d4f6d95..1634ccb04 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -1,4 +1,9 @@ -pub(crate) fn current_migrations() -> Vec<&'static str> { +pub(crate) fn current_migrations(is_mainnet: bool) -> Vec<&'static str> { + let alter_payment_tx_data_add_asset_id = if is_mainnet { + "ALTER TABLE payment_tx_data ADD COLUMN asset_id TEXT NOT NULL DEFAULT '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';" + } else { + "ALTER TABLE payment_tx_data ADD COLUMN asset_id TEXT NOT NULL DEFAULT '144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49';" + }; vec![ "CREATE TABLE IF NOT EXISTS receive_swaps ( id TEXT NOT NULL PRIMARY KEY, @@ -248,5 +253,10 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { ALTER TABLE receive_swaps ADD COLUMN destination_pubkey TEXT; ALTER TABLE send_swaps ADD COLUMN destination_pubkey TEXT; ", + alter_payment_tx_data_add_asset_id, + " + ALTER TABLE payment_tx_data RENAME COLUMN amount_sat TO amount; + UPDATE payment_tx_data SET amount = amount - fees_sat WHERE payment_type = 1; + ", ] } diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 1719c1163..188db1208 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -82,7 +82,12 @@ impl Persister { } fn migrate_main_db(&self) -> Result<()> { - let migrations = Migrations::new(current_migrations().into_iter().map(M::up).collect()); + let migrations = Migrations::new( + current_migrations(self.network.eq(&LiquidNetwork::Mainnet)) + .into_iter() + .map(M::up) + .collect(), + ); let mut conn = self.get_connection()?; migrations.to_latest(&mut conn)?; Ok(()) @@ -104,20 +109,35 @@ 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 == utils::lbtc_asset_id(self.network) { - 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 lbtc_asset_id = utils::lbtc_asset_id(self.network); + let mut balance = tx_balances + .remove(&lbtc_asset_id) + .map(|balance| (lbtc_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 (asset_id, payment_type, amount) = match balance { + Some((asset_id, asset_amount)) => { + let payment_type = match asset_amount >= 0 { + true => PaymentType::Receive, + false => PaymentType::Send, + }; + let mut amount = asset_amount.unsigned_abs(); + if payment_type == PaymentType::Send && asset_id.eq(&lbtc_asset_id.to_string()) { + amount = amount.saturating_sub(tx.fee); + } + (asset_id, payment_type, amount) + } + None => { + log::warn!("Attempted to persist a payment with no balance: tx_id {tx_id}"); + return Ok(()); + } + }; let maybe_script_pubkey = tx .outputs .iter() @@ -130,12 +150,10 @@ impl Persister { PaymentTxData { tx_id: tx_id.clone(), timestamp: tx.timestamp, - amount_sat: amount_sat.unsigned_abs(), + asset_id, + amount, fees_sat: tx.fee, - payment_type: match amount_sat >= 0 { - true => PaymentType::Receive, - false => PaymentType::Send, - }, + payment_type, is_confirmed: is_tx_confirmed, unblinding_data: Some(unblinding_data), }, @@ -153,7 +171,8 @@ impl Persister { let mut stmt = con.prepare( "SELECT tx_id, timestamp, - amount_sat, + asset_id, + amount, fees_sat, payment_type, is_confirmed, @@ -166,11 +185,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: 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,16 +210,18 @@ impl Persister { "INSERT INTO payment_tx_data ( tx_id, timestamp, - amount_sat, + asset_id, + amount, 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, - amount_sat = excluded.amount_sat, + asset_id = excluded.asset_id, + amount = excluded.amount, fees_sat = excluded.fees_sat, payment_type = excluded.payment_type, is_confirmed = excluded.is_confirmed, @@ -208,7 +230,8 @@ impl Persister { ( &ptx.tx_id, ptx.timestamp.or(Some(utils::now())), - ptx.amount_sat, + ptx.asset_id, + ptx.amount, ptx.fees_sat, ptx.payment_type, ptx.is_confirmed, @@ -362,7 +385,8 @@ impl Persister { SELECT ptx.tx_id, ptx.timestamp, - ptx.amount_sat, + ptx.asset_id, + ptx.amount, ptx.fees_sat, ptx.payment_type, ptx.is_confirmed, @@ -407,7 +431,7 @@ impl Persister { cs.pair_fees_json, cs.actual_payer_amount_sat, cs.accepted_receiver_amount_sat, - rtx.amount_sat, + rtx.amount, pd.destination, pd.description, pd.lnurl_info_json @@ -454,69 +478,70 @@ 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: 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_timeout_block_height: Option = row.get(9)?; - let maybe_receive_swap_invoice: Option = row.get(10)?; - let maybe_receive_swap_payment_hash: Option = row.get(11)?; - let maybe_receive_swap_destination_pubkey: Option = row.get(12)?; - let maybe_receive_swap_description: Option = row.get(13)?; - let maybe_receive_swap_preimage: Option = row.get(14)?; - let maybe_receive_swap_payer_amount_sat: Option = row.get(15)?; - let maybe_receive_swap_receiver_amount_sat: Option = row.get(16)?; - let maybe_receive_swap_receiver_state: Option = row.get(17)?; - let maybe_receive_swap_pair_fees_json: Option = row.get(18)?; + let maybe_receive_swap_id: Option = row.get(8)?; + let maybe_receive_swap_created_at: Option = row.get(9)?; + let maybe_receive_swap_timeout_block_height: Option = row.get(10)?; + let maybe_receive_swap_invoice: Option = row.get(11)?; + let maybe_receive_swap_payment_hash: Option = row.get(12)?; + let maybe_receive_swap_destination_pubkey: Option = row.get(13)?; + let maybe_receive_swap_description: Option = row.get(14)?; + let maybe_receive_swap_preimage: Option = row.get(15)?; + let maybe_receive_swap_payer_amount_sat: Option = row.get(16)?; + let maybe_receive_swap_receiver_amount_sat: Option = row.get(17)?; + let maybe_receive_swap_receiver_state: Option = row.get(18)?; + let maybe_receive_swap_pair_fees_json: Option = row.get(19)?; 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(19)?; - let maybe_send_swap_created_at: Option = row.get(20)?; - let maybe_send_swap_timeout_block_height: Option = row.get(21)?; - let maybe_send_swap_invoice: Option = row.get(22)?; - let maybe_send_swap_bolt12_offer: Option = row.get(23)?; - let maybe_send_swap_payment_hash: Option = row.get(24)?; - let maybe_send_swap_destination_pubkey: Option = row.get(25)?; - let maybe_send_swap_description: Option = row.get(26)?; - let maybe_send_swap_preimage: Option = row.get(27)?; - let maybe_send_swap_refund_tx_id: Option = row.get(28)?; - let maybe_send_swap_payer_amount_sat: Option = row.get(29)?; - let maybe_send_swap_receiver_amount_sat: Option = row.get(30)?; - let maybe_send_swap_state: Option = row.get(31)?; - let maybe_send_swap_pair_fees_json: Option = row.get(32)?; + let maybe_send_swap_id: Option = row.get(20)?; + let maybe_send_swap_created_at: Option = row.get(21)?; + let maybe_send_swap_timeout_block_height: Option = row.get(22)?; + let maybe_send_swap_invoice: Option = row.get(23)?; + let maybe_send_swap_bolt12_offer: Option = row.get(24)?; + let maybe_send_swap_payment_hash: Option = row.get(25)?; + let maybe_send_swap_destination_pubkey: Option = row.get(26)?; + let maybe_send_swap_description: Option = row.get(27)?; + let maybe_send_swap_preimage: Option = row.get(28)?; + let maybe_send_swap_refund_tx_id: Option = row.get(29)?; + let maybe_send_swap_payer_amount_sat: Option = row.get(30)?; + let maybe_send_swap_receiver_amount_sat: Option = row.get(31)?; + let maybe_send_swap_state: Option = row.get(32)?; + let maybe_send_swap_pair_fees_json: Option = row.get(33)?; 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(33)?; - let maybe_chain_swap_created_at: Option = row.get(34)?; - let maybe_chain_swap_timeout_block_height: Option = row.get(35)?; - let maybe_chain_swap_direction: Option = row.get(36)?; - let maybe_chain_swap_preimage: Option = row.get(37)?; - let maybe_chain_swap_description: Option = row.get(38)?; - let maybe_chain_swap_refund_tx_id: Option = row.get(39)?; - let maybe_chain_swap_payer_amount_sat: Option = row.get(40)?; - let maybe_chain_swap_receiver_amount_sat: Option = row.get(41)?; - let maybe_chain_swap_claim_address: Option = row.get(42)?; - let maybe_chain_swap_state: Option = row.get(43)?; - let maybe_chain_swap_pair_fees_json: Option = row.get(44)?; + let maybe_chain_swap_id: Option = row.get(34)?; + let maybe_chain_swap_created_at: Option = row.get(35)?; + let maybe_chain_swap_timeout_block_height: Option = row.get(36)?; + let maybe_chain_swap_direction: Option = row.get(37)?; + let maybe_chain_swap_preimage: Option = row.get(38)?; + let maybe_chain_swap_description: Option = row.get(39)?; + let maybe_chain_swap_refund_tx_id: Option = row.get(40)?; + let maybe_chain_swap_payer_amount_sat: Option = row.get(41)?; + let maybe_chain_swap_receiver_amount_sat: Option = row.get(42)?; + let maybe_chain_swap_claim_address: Option = row.get(43)?; + let maybe_chain_swap_state: Option = row.get(44)?; + let maybe_chain_swap_pair_fees_json: Option = row.get(45)?; 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(45)?; - let maybe_chain_swap_accepted_receiver_amount_sat: Option = row.get(46)?; + let maybe_chain_swap_actual_payer_amount_sat: Option = row.get(46)?; + let maybe_chain_swap_accepted_receiver_amount_sat: Option = row.get(47)?; - let maybe_swap_refund_tx_amount_sat: Option = row.get(47)?; + let maybe_swap_refund_tx_amount_sat: Option = row.get(48)?; - let maybe_payment_details_destination: Option = row.get(48)?; - let maybe_payment_details_description: Option = row.get(49)?; - let maybe_payment_details_lnurl_info_json: Option = row.get(50)?; + let maybe_payment_details_destination: Option = row.get(49)?; + let maybe_payment_details_description: Option = row.get(50)?; + let maybe_payment_details_lnurl_info_json: Option = row.get(51)?; let maybe_payment_details_lnurl_info: Option = maybe_payment_details_lnurl_info_json.and_then(|info| serde_json::from_str(&info).ok()); @@ -708,6 +733,11 @@ impl Persister { } } _ => PaymentDetails::Liquid { + asset_id: tx + .clone() + .map_or(utils::lbtc_asset_id(self.network).to_string(), |ptd| { + ptd.asset_id + }), destination: maybe_payment_details_destination .unwrap_or("Destination unknown".to_string()), description: maybe_payment_details_description @@ -851,17 +881,30 @@ 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.direction = 0 AND cs.lockup_address = ? OR cs.direction = 1 AND cs.claim_address = ?)" - .to_string(), - ); - where_params.push(Box::new(address)); - where_params.push(Box::new(address)); + where_clause.push("cs.id IS NOT NULL".to_string()); + if let Some(address) = address { + // Use the lockup address if it's incoming, else use the claim address + where_clause.push( + "(cs.direction = 0 AND cs.lockup_address = ? OR cs.direction = 1 AND cs.claim_address = ?)" + .to_string(), + ); + where_params.push(Box::new(address)); + where_params.push(Box::new(address)); + } } - ListPaymentDetails::Liquid { destination } => { - 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)); + } } } } @@ -874,6 +917,7 @@ mod tests { use anyhow::Result; use crate::{ + model::LiquidNetwork, persist::PaymentTxDetails, prelude::ListPaymentsRequest, test_utils::persist::{ @@ -887,7 +931,7 @@ mod tests { fn test_get_payments() -> Result<()> { create_persister!(storage); - let payment_tx_data = new_payment_tx_data(PaymentType::Send); + let payment_tx_data = new_payment_tx_data(LiquidNetwork::Testnet, PaymentType::Send); storage.insert_or_update_payment( payment_tx_data.clone(), Some(PaymentTxDetails { diff --git a/lib/core/src/receive_swap.rs b/lib/core/src/receive_swap.rs index 4d57f6c34..48cb4d9d4 100644 --- a/lib/core/src/receive_swap.rs +++ b/lib/core/src/receive_swap.rs @@ -333,7 +333,8 @@ impl ReceiveSwapHandler { PaymentTxData { tx_id: claim_tx_id.clone(), timestamp: Some(utils::now()), - amount_sat: swap.receiver_amount_sat, + asset_id: self.config.lbtc_asset_id(), + amount: swap.receiver_amount_sat, fees_sat: 0, payment_type: PaymentType::Receive, is_confirmed: false, diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 34ada20df..6c19bb3bc 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::ops::Not as _; use std::time::Instant; use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; @@ -14,6 +14,7 @@ use futures_util::{StreamExt, TryFutureExt}; use lnurl::auth::SdkLnurlAuthSigner; use log::{debug, error, info, warn}; use lwk_wollet::bitcoin::base64::Engine as _; +use lwk_wollet::elements::AssetId; use lwk_wollet::elements_miniscript::elements::bitcoin::bip32::Xpub; use lwk_wollet::hashes::{sha256, Hash}; use lwk_wollet::secp256k1::Message; @@ -794,10 +795,16 @@ impl LiquidSdk { &self, amount_sat: u64, address: &str, + asset_id: &str, ) -> Result { let fee_sat = self .onchain_wallet - .build_tx(Some(LIQUID_FEE_RATE_MSAT_PER_VBYTE), address, amount_sat) + .build_tx( + Some(LIQUID_FEE_RATE_MSAT_PER_VBYTE), + address, + asset_id, + amount_sat, + ) .await? .all_fees() .values() @@ -821,8 +828,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, + self.config.lbtc_asset_id().as_str(), + ) + .await } async fn estimate_drain_tx_fee( @@ -851,13 +862,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(&self.config.lbtc_asset_id()) => { + self.estimate_drain_tx_fee(Some(amount_sat), Some(address)) + .await + .map_err(|_| PaymentError::InsufficientFunds) + } Err(e) => Err(e), } } @@ -867,8 +883,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, + &self.config.lbtc_asset_id(), + ) + .await } /// Prepares to pay a Lightning invoice via a submarine swap. @@ -879,8 +899,9 @@ impl LiquidSdk { /// * `destination` - Either a Liquid BIP21 URI/address, a BOLT11 invoice or a BOLT12 offer /// * `amount` - The optional amount of type [PayAmount]. Should only be specified /// 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 + /// - [PayAmount::Drain] which uses all Bitcoin funds + /// - [PayAmount::Bitcoin] which sets the amount in satoshi that will be received + /// - [PayAmount::Asset] which sets the amount of an asset that will be received /// /// # Returns /// Returns a [PrepareSendResponse] containing: @@ -895,22 +916,39 @@ 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 { Ok(InputType::LiquidAddress { address: mut liquid_address_data, }) => { - let amount = match (liquid_address_data.amount_sat, req.amount.clone()) { - (None, None) => { + let amount = match ( + liquid_address_data.amount_sat, + liquid_address_data.asset_id, + req.amount.clone(), + ) { + (None, _, None) => { return Err(PaymentError::AmountMissing { err: "Amount must be set when paying to a Liquid address".to_string(), }); } - (Some(bip21_amount_sat), None) => PayAmount::Receiver { - amount_sat: bip21_amount_sat, + (Some(amount_sat), Some(asset_id), None) => { + if asset_id.eq(&self.config.lbtc_asset_id()) { + PayAmount::Bitcoin { + receiver_amount_sat: amount_sat, + } + } else { + PayAmount::Asset { + asset_id, + receiver_amount: amount_sat, + } + } + } + (Some(amount_sat), None, None) => PayAmount::Bitcoin { + receiver_amount_sat: amount_sat, }, - (_, Some(amount)) => amount, + (_, _, Some(amount)) => amount, }; ensure_sdk!( @@ -924,7 +962,7 @@ impl LiquidSdk { } ); - (receiver_amount_sat, fees_sat) = match amount { + (asset_id, receiver_amount_sat, fees_sat) = match amount { PayAmount::Drain => { ensure_sdk!( get_info_res.wallet_info.pending_receive_sat == 0 @@ -939,20 +977,42 @@ impl LiquidSdk { let drain_amount_sat = get_info_res.wallet_info.balance_sat - drain_fees_sat; info!("Drain amount: {drain_amount_sat} sat"); - (drain_amount_sat, drain_fees_sat) + ( + self.config.lbtc_asset_id(), + drain_amount_sat, + drain_fees_sat, + ) + } + PayAmount::Bitcoin { + receiver_amount_sat, + } => { + let asset_id = self.config.lbtc_asset_id(); + let fees_sat = self + .estimate_onchain_tx_or_drain_tx_fee( + receiver_amount_sat, + &liquid_address_data.address, + &asset_id, + ) + .await?; + (asset_id, receiver_amount_sat, fees_sat) } - PayAmount::Receiver { amount_sat } => { + PayAmount::Asset { + asset_id, + receiver_amount, + } => { let fees_sat = self .estimate_onchain_tx_or_drain_tx_fee( - amount_sat, + receiver_amount, &liquid_address_data.address, + &asset_id, ) .await?; - (amount_sat, fees_sat) + (asset_id, receiver_amount, fees_sat) } }; 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, }; @@ -971,7 +1031,10 @@ impl LiquidSdk { "Expected invoice with an amount", ))? / 1000; - if let Some(PayAmount::Receiver { amount_sat }) = req.amount { + if let Some(PayAmount::Bitcoin { + receiver_amount_sat: amount_sat, + }) = req.amount + { ensure_sdk!( receiver_amount_sat == amount_sat, PaymentError::Generic { @@ -982,10 +1045,15 @@ impl LiquidSdk { let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?; + asset_id = self.config.lbtc_asset_id(); 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); @@ -1000,7 +1068,9 @@ impl LiquidSdk { } Ok(InputType::Bolt12Offer { offer }) => { receiver_amount_sat = match req.amount { - Some(PayAmount::Receiver { amount_sat }) => Ok(amount_sat), + Some(PayAmount::Bitcoin { + receiver_amount_sat: amount_sat, + }) => Ok(amount_sat), _ => Err(PaymentError::amount_missing( "Expected PayAmount of type Receiver when processing a Bolt12 offer", )), @@ -1020,6 +1090,7 @@ impl LiquidSdk { let lockup_fees_sat = self .estimate_lockup_tx_or_drain_tx_fee(receiver_amount_sat + boltz_fees_total) .await?; + asset_id = self.config.lbtc_asset_id(); fees_sat = boltz_fees_total + lockup_fees_sat; payment_destination = SendDestination::Bolt12 { @@ -1032,11 +1103,12 @@ impl LiquidSdk { } }; - let payer_amount_sat = receiver_amount_sat + fees_sat; - ensure_sdk!( - payer_amount_sat <= get_info_res.wallet_info.balance_sat, - PaymentError::InsufficientFunds - ); + get_info_res.wallet_info.validate_sufficient_funds( + self.config.network, + receiver_amount_sat, + fees_sat, + &asset_id, + )?; Ok(PrepareSendResponse { destination: payment_destination, @@ -1084,6 +1156,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(), @@ -1096,12 +1171,15 @@ impl LiquidSdk { } ); - let payer_amount_sat = amount_sat + fees_sat; - ensure_sdk!( - payer_amount_sat <= self.get_info().await?.wallet_info.balance_sat, - PaymentError::InsufficientFunds - ); - + self.get_info() + .await? + .wallet_info + .validate_sufficient_funds( + self.config.network, + amount_sat, + *fees_sat, + asset_id, + )?; self.pay_liquid(liquid_address_data.clone(), amount_sat, *fees_sat, true) .await } @@ -1215,9 +1293,11 @@ impl LiquidSdk { let destination = address_data .to_uri() .unwrap_or(address_data.address.clone()); + let asset_id = address_data.asset_id.unwrap_or(self.config.lbtc_asset_id()); 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() })?; @@ -1231,14 +1311,14 @@ impl LiquidSdk { .build_tx_or_drain_tx( Some(LIQUID_FEE_RATE_MSAT_PER_VBYTE), &address_data.address, + &asset_id, receiver_amount_sat, ) .await?; + let tx_id = tx.txid().to_string(); let tx_fees_sat = tx.all_fees().values().sum::(); ensure_sdk!(tx_fees_sat <= fees_sat, PaymentError::InvalidOrExpiredFees); - let tx_id = tx.txid().to_string(); - let payer_amount_sat = receiver_amount_sat + tx_fees_sat; info!( "Built onchain L-BTC tx with receiver_amount_sat = {receiver_amount_sat}, fees_sat = {fees_sat} and txid = {tx_id}" ); @@ -1251,11 +1331,12 @@ impl LiquidSdk { let tx_data = PaymentTxData { tx_id: tx_id.clone(), timestamp: Some(utils::now()), - amount_sat: payer_amount_sat, + amount: receiver_amount_sat, fees_sat, payment_type: PaymentType::Send, is_confirmed: false, unblinding_data: None, + asset_id: asset_id.clone(), }; let description = address_data.message; @@ -1273,6 +1354,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()), }; @@ -1467,7 +1549,7 @@ impl LiquidSdk { /// /// * `req` - the [PreparePayOnchainRequest] containing: /// * `amount` - which can be of two types: [PayAmount::Drain], which uses all funds, - /// and [PayAmount::Receiver], which sets the amount the receiver should receive + /// and [PayAmount::Bitcoin], which sets the amount the receiver should receive /// * `fee_rate_sat_per_vbyte` - the optional fee rate of the Bitcoin claim transaction. Defaults to the swapper estimated claim fee pub async fn prepare_pay_onchain( &self, @@ -1485,7 +1567,9 @@ impl LiquidSdk { info!("Preparing for onchain payment of kind: {:?}", req.amount); let (payer_amount_sat, receiver_amount_sat, total_fees_sat) = match req.amount { - PayAmount::Receiver { amount_sat } => { + PayAmount::Bitcoin { + receiver_amount_sat: amount_sat, + } => { let receiver_amount_sat = amount_sat; let user_lockup_amount_sat_without_service_fee = @@ -1530,6 +1614,11 @@ impl LiquidSdk { (payer_amount_sat, receiver_amount_sat, total_fees_sat) } + PayAmount::Asset { .. } => { + return Err(PaymentError::generic( + "Cannot send an asset to a Bitcoin address", + )) + } }; let res = PreparePayOnchainResponse { @@ -1750,8 +1839,10 @@ 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 + /// * `amount` - The optional amount of type [ReceiveAmount] to be paid. + /// - [ReceiveAmount::Bitcoin] which sets the amount in satoshi that should be paid + /// - [ReceiveAmount::Asset] which sets the amount of an asset that should be paid pub async fn prepare_receive_payment( &self, req: &PrepareReceiveRequest, @@ -1764,8 +1855,18 @@ impl LiquidSdk { let fees_sat; match req.payment_method { PaymentMethod::Lightning => { - let Some(payer_amount_sat) = req.payer_amount_sat else { - return Err(PaymentError::AmountMissing { err: "`payer_amount_sat` must be specified when `PaymentMethod::Lightning` is used.".to_string() }); + let payer_amount_sat = match req.amount { + Some(ReceiveAmount::Asset { .. }) => { + return Err(PaymentError::generic( + "Cannot receive an asset when the payment method is Lightning", + )); + } + Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => payer_amount_sat, + None => { + return Err(PaymentError::generic( + "Bitcoin payer amount must be set when the payment method is Lightning", + )); + } }; let reverse_pair = self .swapper @@ -1790,7 +1891,15 @@ impl LiquidSdk { ); } PaymentMethod::BitcoinAddress => { - let payer_amount_sat = req.payer_amount_sat; + let payer_amount_sat = match req.amount { + Some(ReceiveAmount::Asset { .. }) => { + return Err(PaymentError::generic( + "Cannot receive an asset the payment method is Bitcoin", + )); + } + Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => Some(payer_amount_sat), + None => None, + }; let pair = self.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)?; let claim_fees_sat = pair.fees.claim_estimate(); @@ -1807,14 +1916,18 @@ impl LiquidSdk { debug!("Preparing Chain Receive Swap with: payer_amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}"); } PaymentMethod::LiquidAddress => { + let payer_amount_sat = match req.amount { + Some(ReceiveAmount::Asset { payer_amount, .. }) => payer_amount, + Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => Some(payer_amount_sat), + None => None, + }; 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}"); + debug!("Preparing Liquid Receive with: amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}"); } }; Ok(PrepareReceiveResponse { - payer_amount_sat: req.payer_amount_sat, + amount: req.amount.clone(), fees_sat, payment_method: req.payment_method.clone(), min_payer_amount_sat, @@ -1845,15 +1958,25 @@ impl LiquidSdk { let PrepareReceiveResponse { payment_method, - payer_amount_sat: amount_sat, + amount, fees_sat, .. } = &req.prepare_response; match payment_method { PaymentMethod::Lightning => { - let Some(amount_sat) = amount_sat else { - return Err(PaymentError::AmountMissing { err: "`amount_sat` must be specified when `PaymentMethod::Lightning` is used.".to_string() }); + let amount_sat = match amount.clone() { + Some(ReceiveAmount::Asset { .. }) => { + return Err(PaymentError::generic( + "Cannot receive an asset when the payment method is Lightning", + )); + } + Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => payer_amount_sat, + None => { + return Err(PaymentError::generic( + "Bitcoin payer amount must be set when the payment method is Lightning", + )); + } }; let (description, description_hash) = match ( req.description.clone(), @@ -1870,19 +1993,40 @@ impl LiquidSdk { }) } }; - self.create_receive_swap(*amount_sat, *fees_sat, description, description_hash) + self.create_receive_swap(amount_sat, *fees_sat, description, description_hash) .await } - PaymentMethod::BitcoinAddress => self.receive_onchain(*amount_sat, *fees_sat).await, + PaymentMethod::BitcoinAddress => { + let amount_sat = match amount.clone() { + Some(ReceiveAmount::Asset { .. }) => { + return Err(PaymentError::generic( + "Cannot receive an asset the payment method is Bitcoin", + )); + } + Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => Some(payer_amount_sat), + None => None, + }; + self.receive_onchain(amount_sat, *fees_sat).await + } PaymentMethod::LiquidAddress => { - let address = self.onchain_wallet.next_unused_address().await?.to_string(); + let (asset_id, amount_sat) = match amount.clone() { + Some(ReceiveAmount::Asset { + asset_id, + payer_amount, + }) => (asset_id, payer_amount), + Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => { + (self.config.lbtc_asset_id(), Some(payer_amount_sat)) + } + None => (self.config.lbtc_asset_id(), None), + }; + let address = self.onchain_wallet.next_unused_address().await?.to_string(); let receive_destination = match amount_sat { Some(amount_sat) => LiquidAddressData { address: address.to_string(), network: self.config.network.into(), - amount_sat: Some(*amount_sat), - asset_id: Some(utils::lbtc_asset_id(self.config.network).to_string()), + amount_sat: Some(amount_sat), + asset_id: Some(asset_id.clone()), label: None, message: req.description.clone(), } @@ -2306,15 +2450,20 @@ impl LiquidSdk { let res = self .prepare_receive_payment(&PrepareReceiveRequest { payment_method: PaymentMethod::BitcoinAddress, - payer_amount_sat: Some(req.amount_sat), + amount: Some(ReceiveAmount::Bitcoin { + payer_amount_sat: req.amount_sat, + }), }) .await?; - let Some(amount_sat) = res.payer_amount_sat else { + let Some(ReceiveAmount::Bitcoin { + payer_amount_sat: amount_sat, + }) = res.amount + else { return Err(PaymentError::Generic { err: format!( - "Expected field `amount_sat` from response, got {:?}", - res.payer_amount_sat + "Error preparing receive payment, got amount: {:?}", + res.amount ), }); }; @@ -2519,18 +2668,29 @@ impl LiquidSdk { async fn update_wallet_info(&self) -> Result<()> { let transactions = self.onchain_wallet.transactions().await?; - let mut wallet_amount_sat: i64 = 0; - transactions.into_iter().for_each(|tx| { - tx.balance.into_iter().for_each(|(asset_id, balance)| { - if asset_id == utils::lbtc_asset_id(self.config.network) { - //consider only confirmed unspent outputs (confirmed transactions output reduced by unconfirmed spent outputs) + let asset_balances = transactions + .into_iter() + .fold(BTreeMap::::new(), |mut acc, tx| { + tx.balance.into_iter().for_each(|(asset_id, balance)| { + // Consider only confirmed unspent outputs (confirmed transactions output reduced by unconfirmed spent outputs) if tx.height.is_some() || balance < 0 { - wallet_amount_sat += balance; + *acc.entry(asset_id).or_default() += balance; } - } + }); + acc }) - }); - debug!("Onchain wallet balance: {wallet_amount_sat} sats"); + .into_iter() + .map(|(asset_id, balance)| AssetBalance { + asset_id: asset_id.to_hex(), + balance: balance.unsigned_abs(), + }) + .collect::>(); + let balance_sat = asset_balances + .clone() + .into_iter() + .find(|ab| ab.asset_id.eq(&self.config.lbtc_asset_id())) + .map_or(0, |ab| ab.balance); + debug!("Onchain wallet balance: {balance_sat} sats"); let mut pending_send_sat = 0; let mut pending_receive_sat = 0; @@ -2544,21 +2704,27 @@ impl LiquidSdk { })?; for payment in payments { + let total_sat = if payment.details.is_lbtc_asset_id(self.config.network) { + payment.amount_sat + payment.fees_sat + } else { + payment.fees_sat + }; 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, + None => pending_send_sat += total_sat, }, - PaymentType::Receive => pending_receive_sat += payment.amount_sat, + PaymentType::Receive => pending_receive_sat += total_sat, } } let info_response = WalletInfo { - 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) } @@ -2946,7 +3112,9 @@ impl LiquidSdk { .prepare_receive_payment(&{ PrepareReceiveRequest { payment_method: PaymentMethod::Lightning, - payer_amount_sat: Some(req.amount_msat / 1_000), + amount: Some(ReceiveAmount::Bitcoin { + payer_amount_sat: req.amount_msat / 1_000, + }), } }) .await?; diff --git a/lib/core/src/send_swap.rs b/lib/core/src/send_swap.rs index 12da8641d..69b0d4f23 100644 --- a/lib/core/src/send_swap.rs +++ b/lib/core/src/send_swap.rs @@ -197,6 +197,7 @@ impl SendSwapHandler { .build_tx_or_drain_tx( Some(LIQUID_FEE_RATE_MSAT_PER_VBYTE), &create_response.address, + &self.config.lbtc_asset_id(), create_response.expected_amount, ) .await?; @@ -225,7 +226,8 @@ impl SendSwapHandler { PaymentTxData { tx_id: lockup_tx_id.clone(), timestamp: Some(utils::now()), - amount_sat: swap.payer_amount_sat, + asset_id: self.config.lbtc_asset_id(), + amount: create_response.expected_amount, fees_sat: lockup_tx_fees_sat, payment_type: PaymentType::Send, is_confirmed: false, diff --git a/lib/core/src/test_utils/persist.rs b/lib/core/src/test_utils/persist.rs index 88e040b94..fcf2d2aca 100644 --- a/lib/core/src/test_utils/persist.rs +++ b/lib/core/src/test_utils/persist.rs @@ -12,7 +12,7 @@ use sdk_common::{ }; use crate::{ - model::{PaymentState, PaymentTxData, PaymentType, ReceiveSwap, SendSwap}, + model::{LiquidNetwork, PaymentState, PaymentTxData, PaymentType, ReceiveSwap, SendSwap}, test_utils::generate_random_string, utils, }; @@ -162,11 +162,15 @@ macro_rules! create_persister { } pub(crate) use create_persister; -pub(crate) fn new_payment_tx_data(payment_type: PaymentType) -> PaymentTxData { +pub(crate) fn new_payment_tx_data( + network: LiquidNetwork, + payment_type: PaymentType, +) -> PaymentTxData { PaymentTxData { tx_id: generate_random_string(4), timestamp: None, - amount_sat: 0, + asset_id: utils::lbtc_asset_id(network).to_string(), + amount: 0, fees_sat: 0, payment_type, is_confirmed: false, diff --git a/lib/core/src/test_utils/wallet.rs b/lib/core/src/test_utils/wallet.rs index bf828e7f6..a12a08677 100644 --- a/lib/core/src/test_utils/wallet.rs +++ b/lib/core/src/test_utils/wallet.rs @@ -55,6 +55,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 +74,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 421c87a9e..caf1a4884 100644 --- a/lib/core/src/wallet.rs +++ b/lib/core/src/wallet.rs @@ -10,7 +10,7 @@ use boltz_client::ElementsAddress; 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, @@ -46,6 +46,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 +71,7 @@ pub trait OnchainWallet: Send + Sync { &self, fee_rate_sats_per_kvb: Option, recipient_address: &str, + asset_id: &str, amount_sat: u64, ) -> Result; @@ -203,23 +205,26 @@ 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, - )? + 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) - .enable_ct_discount() - .finish(&lwk_wollet)?; + .enable_ct_discount(); + if asset_id.eq(&self.config.lbtc_asset_id()) { + 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 { @@ -279,14 +284,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(&self.config.lbtc_asset_id()) => { 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 6d6a6ebc6..7f8839649 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 @@ -1675,6 +1686,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_prepare_send_request(raw); } + @protected + ReceiveAmount dco_decode_box_autoadd_receive_amount(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_receive_amount(raw); + } + @protected ReceivePaymentRequest dco_decode_box_autoadd_receive_payment_request(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2023,6 +2040,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 @@ -2065,11 +2088,12 @@ 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( - address: dco_decode_String(raw[1]), + address: dco_decode_opt_String(raw[1]), ); default: throw Exception("unreachable"); @@ -2588,6 +2612,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_box_autoadd_payment(raw); } + @protected + ReceiveAmount? dco_decode_opt_box_autoadd_receive_amount(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_receive_amount(raw); + } + @protected SuccessAction? dco_decode_opt_box_autoadd_success_action(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2641,10 +2671,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Dco (DartCObject based), see doc to use other codecs switch (raw[0]) { case 0: - return PayAmount_Receiver( - amountSat: dco_decode_u_64(raw[1]), + return PayAmount_Bitcoin( + receiverAmountSat: dco_decode_u_64(raw[1]), ); case 1: + return PayAmount_Asset( + assetId: dco_decode_String(raw[1]), + receiverAmount: dco_decode_u_64(raw[2]), + ); + case 2: return PayAmount_Drain(); default: throw Exception("unreachable"); @@ -2703,6 +2738,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( @@ -2887,8 +2923,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final arr = raw as List; if (arr.length != 2) throw Exception('unexpected arr length: expect 2 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]), + amount: dco_decode_opt_box_autoadd_receive_amount(arr[1]), ); } @@ -2899,7 +2935,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); return PrepareReceiveResponse( paymentMethod: dco_decode_payment_method(arr[0]), - payerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[1]), + amount: dco_decode_opt_box_autoadd_receive_amount(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]), @@ -2964,6 +3000,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + ReceiveAmount dco_decode_receive_amount(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return ReceiveAmount_Bitcoin( + payerAmountSat: dco_decode_u_64(raw[1]), + ); + case 1: + return ReceiveAmount_Asset( + assetId: dco_decode_String(raw[1]), + payerAmount: dco_decode_opt_box_autoadd_u_64(raw[2]), + ); + default: + throw Exception("unreachable"); + } + } + @protected ReceivePaymentRequest dco_decode_receive_payment_request(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -3299,13 +3353,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { WalletInfo dco_decode_wallet_info(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); return WalletInfo( balanceSat: dco_decode_u_64(arr[0]), pendingSendSat: dco_decode_u_64(arr[1]), pendingReceiveSat: dco_decode_u_64(arr[2]), fingerprint: dco_decode_String(arr[3]), pubkey: dco_decode_String(arr[4]), + assetBalances: dco_decode_list_asset_balance(arr[5]), ); } @@ -3421,6 +3476,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 @@ -3715,6 +3778,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_prepare_send_request(deserializer)); } + @protected + ReceiveAmount sse_decode_box_autoadd_receive_amount(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_receive_amount(deserializer)); + } + @protected ReceivePaymentRequest sse_decode_box_autoadd_receive_payment_request(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4056,6 +4125,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 @@ -4135,10 +4216,11 @@ 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); + var var_address = sse_decode_opt_String(deserializer); return ListPaymentDetails_Bitcoin(address: var_address); default: throw UnimplementedError(''); @@ -4716,6 +4798,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + ReceiveAmount? sse_decode_opt_box_autoadd_receive_amount(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_receive_amount(deserializer)); + } else { + return null; + } + } + @protected SuccessAction? sse_decode_opt_box_autoadd_success_action(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4811,9 +4904,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var tag_ = sse_decode_i_32(deserializer); switch (tag_) { case 0: - var var_amountSat = sse_decode_u_64(deserializer); - return PayAmount_Receiver(amountSat: var_amountSat); + var var_receiverAmountSat = sse_decode_u_64(deserializer); + return PayAmount_Bitcoin(receiverAmountSat: var_receiverAmountSat); case 1: + var var_assetId = sse_decode_String(deserializer); + var var_receiverAmount = sse_decode_u_64(deserializer); + return PayAmount_Asset(assetId: var_assetId, receiverAmount: var_receiverAmount); + case 2: return PayAmount_Drain(); default: throw UnimplementedError(''); @@ -4887,7 +4984,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); @@ -5062,23 +5161,23 @@ 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_amount = sse_decode_opt_box_autoadd_receive_amount(deserializer); + return PrepareReceiveRequest(paymentMethod: var_paymentMethod, amount: var_amount); } @protected PrepareReceiveResponse sse_decode_prepare_receive_response(SseDeserializer deserializer) { // 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_amount = sse_decode_opt_box_autoadd_receive_amount(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); var var_swapperFeerate = sse_decode_opt_box_autoadd_f_64(deserializer); return PrepareReceiveResponse( paymentMethod: var_paymentMethod, - payerAmountSat: var_payerAmountSat, + amount: var_amount, feesSat: var_feesSat, minPayerAmountSat: var_minPayerAmountSat, maxPayerAmountSat: var_maxPayerAmountSat, @@ -5130,6 +5229,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return Rate(coin: var_coin, value: var_value); } + @protected + ReceiveAmount sse_decode_receive_amount(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_payerAmountSat = sse_decode_u_64(deserializer); + return ReceiveAmount_Bitcoin(payerAmountSat: var_payerAmountSat); + case 1: + var var_assetId = sse_decode_String(deserializer); + var var_payerAmount = sse_decode_opt_box_autoadd_u_64(deserializer); + return ReceiveAmount_Asset(assetId: var_assetId, payerAmount: var_payerAmount); + default: + throw UnimplementedError(''); + } + } + @protected ReceivePaymentRequest sse_decode_receive_payment_request(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5434,12 +5551,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 WalletInfo( balanceSat: var_balanceSat, pendingSendSat: var_pendingSendSat, pendingReceiveSat: var_pendingReceiveSat, fingerprint: var_fingerprint, - pubkey: var_pubkey); + pubkey: var_pubkey, + assetBalances: var_assetBalances); } @protected @@ -5651,6 +5770,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 @@ -5942,6 +6068,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_prepare_send_request(self, serializer); } + @protected + void sse_encode_box_autoadd_receive_amount(ReceiveAmount self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_receive_amount(self, serializer); + } + @protected void sse_encode_box_autoadd_receive_payment_request(ReceivePaymentRequest self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6226,6 +6358,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 @@ -6284,12 +6425,13 @@ 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); + sse_encode_opt_String(address, serializer); } } @@ -6746,6 +6888,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_receive_amount(ReceiveAmount? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_receive_amount(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_success_action(SuccessAction? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6831,11 +6983,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_pay_amount(PayAmount self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs switch (self) { - case PayAmount_Receiver(amountSat: final amountSat): + case PayAmount_Bitcoin(receiverAmountSat: final receiverAmountSat): sse_encode_i_32(0, serializer); - sse_encode_u_64(amountSat, serializer); - case PayAmount_Drain(): + sse_encode_u_64(receiverAmountSat, serializer); + case PayAmount_Asset(assetId: final assetId, receiverAmount: final receiverAmount): sse_encode_i_32(1, serializer); + sse_encode_String(assetId, serializer); + sse_encode_u_64(receiverAmount, serializer); + case PayAmount_Drain(): + sse_encode_i_32(2, serializer); } } @@ -6890,10 +7046,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, @@ -7042,15 +7203,15 @@ 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_receive_amount(self.amount, serializer); } @protected void sse_encode_prepare_receive_response(PrepareReceiveResponse self, SseSerializer serializer) { // 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_box_autoadd_receive_amount(self.amount, 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); @@ -7094,6 +7255,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_f_64(self.value, serializer); } + @protected + void sse_encode_receive_amount(ReceiveAmount self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case ReceiveAmount_Bitcoin(payerAmountSat: final payerAmountSat): + sse_encode_i_32(0, serializer); + sse_encode_u_64(payerAmountSat, serializer); + case ReceiveAmount_Asset(assetId: final assetId, payerAmount: final payerAmount): + sse_encode_i_32(1, serializer); + sse_encode_String(assetId, serializer); + sse_encode_opt_box_autoadd_u_64(payerAmount, serializer); + } + } + @protected void sse_encode_receive_payment_request(ReceivePaymentRequest self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -7344,6 +7519,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); } } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 542dcd615..c733f0306 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); @@ -203,6 +206,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected PrepareSendRequest dco_decode_box_autoadd_prepare_send_request(dynamic raw); + @protected + ReceiveAmount dco_decode_box_autoadd_receive_amount(dynamic raw); + @protected ReceivePaymentRequest dco_decode_box_autoadd_receive_payment_request(dynamic raw); @@ -305,6 +311,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); @@ -452,6 +461,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Payment? dco_decode_opt_box_autoadd_payment(dynamic raw); + @protected + ReceiveAmount? dco_decode_opt_box_autoadd_receive_amount(dynamic raw); + @protected SuccessAction? dco_decode_opt_box_autoadd_success_action(dynamic raw); @@ -539,6 +551,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Rate dco_decode_rate(dynamic raw); + @protected + ReceiveAmount dco_decode_receive_amount(dynamic raw); + @protected ReceivePaymentRequest dco_decode_receive_payment_request(dynamic raw); @@ -662,6 +677,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); @@ -804,6 +822,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected PrepareSendRequest sse_decode_box_autoadd_prepare_send_request(SseDeserializer deserializer); + @protected + ReceiveAmount sse_decode_box_autoadd_receive_amount(SseDeserializer deserializer); + @protected ReceivePaymentRequest sse_decode_box_autoadd_receive_payment_request(SseDeserializer deserializer); @@ -908,6 +929,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); @@ -1055,6 +1079,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Payment? sse_decode_opt_box_autoadd_payment(SseDeserializer deserializer); + @protected + ReceiveAmount? sse_decode_opt_box_autoadd_receive_amount(SseDeserializer deserializer); + @protected SuccessAction? sse_decode_opt_box_autoadd_success_action(SseDeserializer deserializer); @@ -1142,6 +1169,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Rate sse_decode_rate(SseDeserializer deserializer); + @protected + ReceiveAmount sse_decode_receive_amount(SseDeserializer deserializer); + @protected ReceivePaymentRequest sse_decode_receive_payment_request(SseDeserializer deserializer); @@ -1607,6 +1637,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ptr; } + @protected + ffi.Pointer cst_encode_box_autoadd_receive_amount(ReceiveAmount raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ptr = wire.cst_new_box_autoadd_receive_amount(); + cst_api_fill_to_wire_receive_amount(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer cst_encode_box_autoadd_receive_payment_request( ReceivePaymentRequest raw) { @@ -1720,6 +1758,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) { @@ -1905,6 +1953,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw == null ? ffi.nullptr : cst_encode_box_autoadd_payment(raw); } + @protected + ffi.Pointer cst_encode_opt_box_autoadd_receive_amount(ReceiveAmount? raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + return raw == null ? ffi.nullptr : cst_encode_box_autoadd_receive_amount(raw); + } + @protected ffi.Pointer cst_encode_opt_box_autoadd_success_action(SuccessAction? raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -2023,6 +2077,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); @@ -2277,6 +2337,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { cst_api_fill_to_wire_prepare_send_request(apiObj, wireObj.ref); } + @protected + void cst_api_fill_to_wire_box_autoadd_receive_amount( + ReceiveAmount apiObj, ffi.Pointer wireObj) { + cst_api_fill_to_wire_receive_amount(apiObj, wireObj.ref); + } + @protected void cst_api_fill_to_wire_box_autoadd_receive_payment_request( ReceivePaymentRequest apiObj, ffi.Pointer wireObj) { @@ -2531,13 +2597,15 @@ 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; } if (apiObj is ListPaymentDetails_Bitcoin) { - var pre_address = cst_encode_String(apiObj.address); + var pre_address = cst_encode_opt_String(apiObj.address); wireObj.tag = 1; wireObj.kind.Bitcoin.address = pre_address; return; @@ -2905,14 +2973,22 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_pay_amount(PayAmount apiObj, wire_cst_pay_amount wireObj) { - if (apiObj is PayAmount_Receiver) { - var pre_amount_sat = cst_encode_u_64(apiObj.amountSat); + if (apiObj is PayAmount_Bitcoin) { + var pre_receiver_amount_sat = cst_encode_u_64(apiObj.receiverAmountSat); wireObj.tag = 0; - wireObj.kind.Receiver.amount_sat = pre_amount_sat; + wireObj.kind.Bitcoin.receiver_amount_sat = pre_receiver_amount_sat; return; } - if (apiObj is PayAmount_Drain) { + if (apiObj is PayAmount_Asset) { + var pre_asset_id = cst_encode_String(apiObj.assetId); + var pre_receiver_amount = cst_encode_u_64(apiObj.receiverAmount); wireObj.tag = 1; + wireObj.kind.Asset.asset_id = pre_asset_id; + wireObj.kind.Asset.receiver_amount = pre_receiver_amount; + return; + } + if (apiObj is PayAmount_Drain) { + wireObj.tag = 2; return; } } @@ -2969,9 +3045,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) { @@ -3156,15 +3234,15 @@ 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.amount = cst_encode_opt_box_autoadd_receive_amount(apiObj.amount); } @protected void cst_api_fill_to_wire_prepare_receive_response( 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.amount = cst_encode_opt_box_autoadd_receive_amount(apiObj.amount); 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); @@ -3207,6 +3285,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.value = cst_encode_f_64(apiObj.value); } + @protected + void cst_api_fill_to_wire_receive_amount(ReceiveAmount apiObj, wire_cst_receive_amount wireObj) { + if (apiObj is ReceiveAmount_Bitcoin) { + var pre_payer_amount_sat = cst_encode_u_64(apiObj.payerAmountSat); + wireObj.tag = 0; + wireObj.kind.Bitcoin.payer_amount_sat = pre_payer_amount_sat; + return; + } + if (apiObj is ReceiveAmount_Asset) { + var pre_asset_id = cst_encode_String(apiObj.assetId); + var pre_payer_amount = cst_encode_opt_box_autoadd_u_64(apiObj.payerAmount); + wireObj.tag = 1; + wireObj.kind.Asset.asset_id = pre_asset_id; + wireObj.kind.Asset.payer_amount = pre_payer_amount; + return; + } + } + @protected void cst_api_fill_to_wire_receive_payment_request( ReceivePaymentRequest apiObj, wire_cst_receive_payment_request wireObj) { @@ -3460,6 +3556,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 @@ -3554,6 +3651,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); @@ -3702,6 +3802,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_prepare_send_request(PrepareSendRequest self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_receive_amount(ReceiveAmount self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_receive_payment_request(ReceivePaymentRequest self, SseSerializer serializer); @@ -3807,6 +3910,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); @@ -3955,6 +4061,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_opt_box_autoadd_payment(Payment? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_receive_amount(ReceiveAmount? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_success_action(SuccessAction? self, SseSerializer serializer); @@ -4043,6 +4152,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_rate(Rate self, SseSerializer serializer); + @protected + void sse_encode_receive_amount(ReceiveAmount self, SseSerializer serializer); + @protected void sse_encode_receive_payment_request(ReceivePaymentRequest self, SseSerializer serializer); @@ -5398,6 +5510,16 @@ class RustLibWire implements BaseWire { late final _cst_new_box_autoadd_prepare_send_request = _cst_new_box_autoadd_prepare_send_requestPtr .asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_receive_amount() { + return _cst_new_box_autoadd_receive_amount(); + } + + late final _cst_new_box_autoadd_receive_amountPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_receive_amount'); + late final _cst_new_box_autoadd_receive_amount = + _cst_new_box_autoadd_receive_amountPtr.asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_receive_payment_request() { return _cst_new_box_autoadd_receive_payment_request(); } @@ -5540,6 +5662,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, ) { @@ -5826,6 +5962,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; } @@ -6193,13 +6331,22 @@ final class wire_cst_prepare_ln_url_pay_request extends ffi.Struct { external ffi.Pointer validate_success_action_url; } -final class wire_cst_PayAmount_Receiver extends ffi.Struct { +final class wire_cst_PayAmount_Bitcoin extends ffi.Struct { @ffi.Uint64() - external int amount_sat; + external int receiver_amount_sat; +} + +final class wire_cst_PayAmount_Asset extends ffi.Struct { + external ffi.Pointer asset_id; + + @ffi.Uint64() + external int receiver_amount; } final class PayAmountKind extends ffi.Union { - external wire_cst_PayAmount_Receiver Receiver; + external wire_cst_PayAmount_Bitcoin Bitcoin; + + external wire_cst_PayAmount_Asset Asset; } final class wire_cst_pay_amount extends ffi.Struct { @@ -6215,11 +6362,35 @@ final class wire_cst_prepare_pay_onchain_request extends ffi.Struct { external ffi.Pointer fee_rate_sat_per_vbyte; } -final class wire_cst_prepare_receive_request extends ffi.Struct { - external ffi.Pointer payer_amount_sat; +final class wire_cst_ReceiveAmount_Bitcoin extends ffi.Struct { + @ffi.Uint64() + external int payer_amount_sat; +} + +final class wire_cst_ReceiveAmount_Asset extends ffi.Struct { + external ffi.Pointer asset_id; + external ffi.Pointer payer_amount; +} + +final class ReceiveAmountKind extends ffi.Union { + external wire_cst_ReceiveAmount_Bitcoin Bitcoin; + + external wire_cst_ReceiveAmount_Asset Asset; +} + +final class wire_cst_receive_amount extends ffi.Struct { + @ffi.Int32() + external int tag; + + external ReceiveAmountKind kind; +} + +final class wire_cst_prepare_receive_request extends ffi.Struct { @ffi.Int32() external int payment_method; + + external ffi.Pointer amount; } final class wire_cst_prepare_refund_request extends ffi.Struct { @@ -6241,7 +6412,7 @@ final class wire_cst_prepare_receive_response extends ffi.Struct { @ffi.Int32() external int payment_method; - external ffi.Pointer payer_amount_sat; + external ffi.Pointer amount; @ffi.Uint64() external int fees_sat; @@ -6392,6 +6563,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 { @@ -6599,6 +6772,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; @@ -6721,6 +6908,8 @@ final class wire_cst_wallet_info extends ffi.Struct { external ffi.Pointer fingerprint; external ffi.Pointer pubkey; + + external ffi.Pointer asset_balances; } final class wire_cst_get_info_response extends ffi.Struct { diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 7715ff729..b687d50e4 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. @@ -429,14 +451,19 @@ enum LiquidNetwork { sealed class ListPaymentDetails with _$ListPaymentDetails { const ListPaymentDetails._(); - /// The Liquid BIP21 URI or address of the payment + /// A Liquid payment const factory ListPaymentDetails.liquid({ - required String destination, + /// Optional asset id + String? assetId, + + /// Optional BIP21 URI or address + String? destination, }) = ListPaymentDetails_Liquid; - /// The Bitcoin address of the payment + /// A Bitcoin payment const factory ListPaymentDetails.bitcoin({ - required String address, + /// Optional address + String? address, }) = ListPaymentDetails_Bitcoin; } @@ -637,11 +664,17 @@ sealed class PayAmount with _$PayAmount { const PayAmount._(); /// The amount in satoshi that will be received - const factory PayAmount.receiver({ - required BigInt amountSat, - }) = PayAmount_Receiver; + const factory PayAmount.bitcoin({ + required BigInt receiverAmountSat, + }) = PayAmount_Bitcoin; + + /// The amount of an asset that will be received + const factory PayAmount.asset({ + required String assetId, + required BigInt receiverAmount, + }) = PayAmount_Asset; - /// Indicates that all available funds should be sent + /// Indicates that all available Bitcoin funds should be sent const factory PayAmount.drain() = PayAmount_Drain; } @@ -814,6 +847,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 @@ -1111,30 +1147,32 @@ class PreparePayOnchainResponse { /// An argument when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. class PrepareReceiveRequest { - final BigInt? payerAmountSat; final PaymentMethod paymentMethod; + /// The amount to be paid in either Bitcoin or another asset + final ReceiveAmount? amount; + const PrepareReceiveRequest({ - this.payerAmountSat, required this.paymentMethod, + this.amount, }); @override - int get hashCode => payerAmountSat.hashCode ^ paymentMethod.hashCode; + int get hashCode => paymentMethod.hashCode ^ amount.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is PrepareReceiveRequest && runtimeType == other.runtimeType && - payerAmountSat == other.payerAmountSat && - paymentMethod == other.paymentMethod; + paymentMethod == other.paymentMethod && + amount == other.amount; } /// Returned when calling [crate::sdk::LiquidSdk::prepare_receive_payment]. class PrepareReceiveResponse { final PaymentMethod paymentMethod; - final BigInt? payerAmountSat; + final ReceiveAmount? amount; /// Generally represents the total fees that would be paid to send or receive this payment. /// @@ -1163,7 +1201,7 @@ class PrepareReceiveResponse { const PrepareReceiveResponse({ required this.paymentMethod, - this.payerAmountSat, + this.amount, required this.feesSat, this.minPayerAmountSat, this.maxPayerAmountSat, @@ -1173,7 +1211,7 @@ class PrepareReceiveResponse { @override int get hashCode => paymentMethod.hashCode ^ - payerAmountSat.hashCode ^ + amount.hashCode ^ feesSat.hashCode ^ minPayerAmountSat.hashCode ^ maxPayerAmountSat.hashCode ^ @@ -1185,7 +1223,7 @@ class PrepareReceiveResponse { other is PrepareReceiveResponse && runtimeType == other.runtimeType && paymentMethod == other.paymentMethod && - payerAmountSat == other.payerAmountSat && + amount == other.amount && feesSat == other.feesSat && minPayerAmountSat == other.minPayerAmountSat && maxPayerAmountSat == other.maxPayerAmountSat && @@ -1296,6 +1334,22 @@ class PrepareSendResponse { feesSat == other.feesSat; } +@freezed +sealed class ReceiveAmount with _$ReceiveAmount { + const ReceiveAmount._(); + + /// The amount in satoshi that should be paid + const factory ReceiveAmount.bitcoin({ + required BigInt payerAmountSat, + }) = ReceiveAmount_Bitcoin; + + /// The amount of an asset that should be paid + const factory ReceiveAmount.asset({ + required String assetId, + BigInt? payerAmount, + }) = ReceiveAmount_Asset; +} + /// An argument when calling [crate::sdk::LiquidSdk::receive_payment]. class ReceivePaymentRequest { final PrepareReceiveResponse prepareResponse; @@ -1601,12 +1655,16 @@ class WalletInfo { /// The wallet's pubkey. Used to verify signed messages. final String pubkey; + /// Asset balances of non Liquid Bitcoin assets + final List assetBalances; + const WalletInfo({ required this.balanceSat, required this.pendingSendSat, required this.pendingReceiveSat, required this.fingerprint, required this.pubkey, + required this.assetBalances, }); @override @@ -1615,7 +1673,8 @@ class WalletInfo { pendingSendSat.hashCode ^ pendingReceiveSat.hashCode ^ fingerprint.hashCode ^ - pubkey.hashCode; + pubkey.hashCode ^ + assetBalances.hashCode; @override bool operator ==(Object other) => @@ -1626,5 +1685,6 @@ class WalletInfo { pendingSendSat == other.pendingSendSat && pendingReceiveSat == other.pendingReceiveSat && fingerprint == other.fingerprint && - pubkey == other.pubkey; + pubkey == other.pubkey && + assetBalances == other.assetBalances; } diff --git a/packages/dart/lib/src/model.freezed.dart b/packages/dart/lib/src/model.freezed.dart index f67de023f..a9efc8f03 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,19 @@ 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._(); + /// Optional asset id @override - final String destination; + final String? assetId; + + /// Optional BIP21 URI or address + @override + final String? destination; @override String toString() { - return 'ListPaymentDetails.liquid(destination: $destination)'; + return 'ListPaymentDetails.liquid(assetId: $assetId, destination: $destination)'; } @override @@ -215,11 +225,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 +242,15 @@ 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; + /// Optional asset id + String? get assetId; + + /// Optional BIP21 URI or address + String? get destination; /// Create a copy of ListPaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -250,7 +265,7 @@ abstract class _$$ListPaymentDetails_BitcoinImplCopyWith<$Res> { _$ListPaymentDetails_BitcoinImpl value, $Res Function(_$ListPaymentDetails_BitcoinImpl) then) = __$$ListPaymentDetails_BitcoinImplCopyWithImpl<$Res>; @useResult - $Res call({String address}); + $Res call({String? address}); } /// @nodoc @@ -266,13 +281,13 @@ class __$$ListPaymentDetails_BitcoinImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? address = null, + Object? address = freezed, }) { return _then(_$ListPaymentDetails_BitcoinImpl( - address: null == address + address: freezed == address ? _value.address : address // ignore: cast_nullable_to_non_nullable - as String, + as String?, )); } } @@ -280,10 +295,11 @@ class __$$ListPaymentDetails_BitcoinImplCopyWithImpl<$Res> /// @nodoc class _$ListPaymentDetails_BitcoinImpl extends ListPaymentDetails_Bitcoin { - const _$ListPaymentDetails_BitcoinImpl({required this.address}) : super._(); + const _$ListPaymentDetails_BitcoinImpl({this.address}) : super._(); + /// Optional address @override - final String address; + final String? address; @override String toString() { @@ -311,11 +327,11 @@ class _$ListPaymentDetails_BitcoinImpl extends ListPaymentDetails_Bitcoin { } abstract class ListPaymentDetails_Bitcoin extends ListPaymentDetails { - const factory ListPaymentDetails_Bitcoin({required final String address}) = - _$ListPaymentDetails_BitcoinImpl; + const factory ListPaymentDetails_Bitcoin({final String? address}) = _$ListPaymentDetails_BitcoinImpl; const ListPaymentDetails_Bitcoin._() : super._(); - String get address; + /// Optional address + String? get address; /// Create a copy of ListPaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -616,20 +632,20 @@ class _$PayAmountCopyWithImpl<$Res, $Val extends PayAmount> implements $PayAmoun } /// @nodoc -abstract class _$$PayAmount_ReceiverImplCopyWith<$Res> { - factory _$$PayAmount_ReceiverImplCopyWith( - _$PayAmount_ReceiverImpl value, $Res Function(_$PayAmount_ReceiverImpl) then) = - __$$PayAmount_ReceiverImplCopyWithImpl<$Res>; +abstract class _$$PayAmount_BitcoinImplCopyWith<$Res> { + factory _$$PayAmount_BitcoinImplCopyWith( + _$PayAmount_BitcoinImpl value, $Res Function(_$PayAmount_BitcoinImpl) then) = + __$$PayAmount_BitcoinImplCopyWithImpl<$Res>; @useResult - $Res call({BigInt amountSat}); + $Res call({BigInt receiverAmountSat}); } /// @nodoc -class __$$PayAmount_ReceiverImplCopyWithImpl<$Res> - extends _$PayAmountCopyWithImpl<$Res, _$PayAmount_ReceiverImpl> - implements _$$PayAmount_ReceiverImplCopyWith<$Res> { - __$$PayAmount_ReceiverImplCopyWithImpl( - _$PayAmount_ReceiverImpl _value, $Res Function(_$PayAmount_ReceiverImpl) _then) +class __$$PayAmount_BitcoinImplCopyWithImpl<$Res> + extends _$PayAmountCopyWithImpl<$Res, _$PayAmount_BitcoinImpl> + implements _$$PayAmount_BitcoinImplCopyWith<$Res> { + __$$PayAmount_BitcoinImplCopyWithImpl( + _$PayAmount_BitcoinImpl _value, $Res Function(_$PayAmount_BitcoinImpl) _then) : super(_value, _then); /// Create a copy of PayAmount @@ -637,12 +653,12 @@ class __$$PayAmount_ReceiverImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? amountSat = null, + Object? receiverAmountSat = null, }) { - return _then(_$PayAmount_ReceiverImpl( - amountSat: null == amountSat - ? _value.amountSat - : amountSat // ignore: cast_nullable_to_non_nullable + return _then(_$PayAmount_BitcoinImpl( + receiverAmountSat: null == receiverAmountSat + ? _value.receiverAmountSat + : receiverAmountSat // ignore: cast_nullable_to_non_nullable as BigInt, )); } @@ -650,50 +666,138 @@ class __$$PayAmount_ReceiverImplCopyWithImpl<$Res> /// @nodoc -class _$PayAmount_ReceiverImpl extends PayAmount_Receiver { - const _$PayAmount_ReceiverImpl({required this.amountSat}) : super._(); +class _$PayAmount_BitcoinImpl extends PayAmount_Bitcoin { + const _$PayAmount_BitcoinImpl({required this.receiverAmountSat}) : super._(); @override - final BigInt amountSat; + final BigInt receiverAmountSat; @override String toString() { - return 'PayAmount.receiver(amountSat: $amountSat)'; + return 'PayAmount.bitcoin(receiverAmountSat: $receiverAmountSat)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$PayAmount_ReceiverImpl && - (identical(other.amountSat, amountSat) || other.amountSat == amountSat)); + other is _$PayAmount_BitcoinImpl && + (identical(other.receiverAmountSat, receiverAmountSat) || + other.receiverAmountSat == receiverAmountSat)); } @override - int get hashCode => Object.hash(runtimeType, amountSat); + int get hashCode => Object.hash(runtimeType, receiverAmountSat); /// Create a copy of PayAmount /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$PayAmount_ReceiverImplCopyWith<_$PayAmount_ReceiverImpl> get copyWith => - __$$PayAmount_ReceiverImplCopyWithImpl<_$PayAmount_ReceiverImpl>(this, _$identity); + _$$PayAmount_BitcoinImplCopyWith<_$PayAmount_BitcoinImpl> get copyWith => + __$$PayAmount_BitcoinImplCopyWithImpl<_$PayAmount_BitcoinImpl>(this, _$identity); } -abstract class PayAmount_Receiver extends PayAmount { - const factory PayAmount_Receiver({required final BigInt amountSat}) = _$PayAmount_ReceiverImpl; - const PayAmount_Receiver._() : super._(); +abstract class PayAmount_Bitcoin extends PayAmount { + const factory PayAmount_Bitcoin({required final BigInt receiverAmountSat}) = _$PayAmount_BitcoinImpl; + const PayAmount_Bitcoin._() : super._(); - BigInt get amountSat; + BigInt get receiverAmountSat; /// Create a copy of PayAmount /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - _$$PayAmount_ReceiverImplCopyWith<_$PayAmount_ReceiverImpl> get copyWith => + _$$PayAmount_BitcoinImplCopyWith<_$PayAmount_BitcoinImpl> get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$PayAmount_AssetImplCopyWith<$Res> { + factory _$$PayAmount_AssetImplCopyWith( + _$PayAmount_AssetImpl value, $Res Function(_$PayAmount_AssetImpl) then) = + __$$PayAmount_AssetImplCopyWithImpl<$Res>; + @useResult + $Res call({String assetId, BigInt receiverAmount}); +} + +/// @nodoc +class __$$PayAmount_AssetImplCopyWithImpl<$Res> extends _$PayAmountCopyWithImpl<$Res, _$PayAmount_AssetImpl> + implements _$$PayAmount_AssetImplCopyWith<$Res> { + __$$PayAmount_AssetImplCopyWithImpl( + _$PayAmount_AssetImpl _value, $Res Function(_$PayAmount_AssetImpl) _then) + : super(_value, _then); + + /// Create a copy of PayAmount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? assetId = null, + Object? receiverAmount = null, + }) { + return _then(_$PayAmount_AssetImpl( + assetId: null == assetId + ? _value.assetId + : assetId // ignore: cast_nullable_to_non_nullable + as String, + receiverAmount: null == receiverAmount + ? _value.receiverAmount + : receiverAmount // ignore: cast_nullable_to_non_nullable + as BigInt, + )); + } +} + +/// @nodoc + +class _$PayAmount_AssetImpl extends PayAmount_Asset { + const _$PayAmount_AssetImpl({required this.assetId, required this.receiverAmount}) : super._(); + + @override + final String assetId; + @override + final BigInt receiverAmount; + + @override + String toString() { + return 'PayAmount.asset(assetId: $assetId, receiverAmount: $receiverAmount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PayAmount_AssetImpl && + (identical(other.assetId, assetId) || other.assetId == assetId) && + (identical(other.receiverAmount, receiverAmount) || other.receiverAmount == receiverAmount)); + } + + @override + int get hashCode => Object.hash(runtimeType, assetId, receiverAmount); + + /// Create a copy of PayAmount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PayAmount_AssetImplCopyWith<_$PayAmount_AssetImpl> get copyWith => + __$$PayAmount_AssetImplCopyWithImpl<_$PayAmount_AssetImpl>(this, _$identity); +} + +abstract class PayAmount_Asset extends PayAmount { + const factory PayAmount_Asset({required final String assetId, required final BigInt receiverAmount}) = + _$PayAmount_AssetImpl; + const PayAmount_Asset._() : super._(); + + String get assetId; + BigInt get receiverAmount; + + /// Create a copy of PayAmount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PayAmount_AssetImplCopyWith<_$PayAmount_AssetImpl> get copyWith => throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$PayAmount_DrainImplCopyWith<$Res> { factory _$$PayAmount_DrainImplCopyWith( @@ -1038,7 +1142,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 @@ -1056,6 +1160,7 @@ class __$$PaymentDetails_LiquidImplCopyWithImpl<$Res> $Res call({ Object? destination = null, Object? description = null, + Object? assetId = null, }) { return _then(_$PaymentDetails_LiquidImpl( destination: null == destination @@ -1066,6 +1171,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, )); } } @@ -1073,7 +1182,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 @@ -1083,9 +1194,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 @@ -1094,11 +1209,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. @@ -1111,7 +1227,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 @@ -1121,6 +1239,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 @@ -1303,6 +1424,196 @@ abstract class PaymentDetails_Bitcoin extends PaymentDetails { throw _privateConstructorUsedError; } +/// @nodoc +mixin _$ReceiveAmount {} + +/// @nodoc +abstract class $ReceiveAmountCopyWith<$Res> { + factory $ReceiveAmountCopyWith(ReceiveAmount value, $Res Function(ReceiveAmount) then) = + _$ReceiveAmountCopyWithImpl<$Res, ReceiveAmount>; +} + +/// @nodoc +class _$ReceiveAmountCopyWithImpl<$Res, $Val extends ReceiveAmount> implements $ReceiveAmountCopyWith<$Res> { + _$ReceiveAmountCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ReceiveAmount_BitcoinImplCopyWith<$Res> { + factory _$$ReceiveAmount_BitcoinImplCopyWith( + _$ReceiveAmount_BitcoinImpl value, $Res Function(_$ReceiveAmount_BitcoinImpl) then) = + __$$ReceiveAmount_BitcoinImplCopyWithImpl<$Res>; + @useResult + $Res call({BigInt payerAmountSat}); +} + +/// @nodoc +class __$$ReceiveAmount_BitcoinImplCopyWithImpl<$Res> + extends _$ReceiveAmountCopyWithImpl<$Res, _$ReceiveAmount_BitcoinImpl> + implements _$$ReceiveAmount_BitcoinImplCopyWith<$Res> { + __$$ReceiveAmount_BitcoinImplCopyWithImpl( + _$ReceiveAmount_BitcoinImpl _value, $Res Function(_$ReceiveAmount_BitcoinImpl) _then) + : super(_value, _then); + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? payerAmountSat = null, + }) { + return _then(_$ReceiveAmount_BitcoinImpl( + payerAmountSat: null == payerAmountSat + ? _value.payerAmountSat + : payerAmountSat // ignore: cast_nullable_to_non_nullable + as BigInt, + )); + } +} + +/// @nodoc + +class _$ReceiveAmount_BitcoinImpl extends ReceiveAmount_Bitcoin { + const _$ReceiveAmount_BitcoinImpl({required this.payerAmountSat}) : super._(); + + @override + final BigInt payerAmountSat; + + @override + String toString() { + return 'ReceiveAmount.bitcoin(payerAmountSat: $payerAmountSat)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ReceiveAmount_BitcoinImpl && + (identical(other.payerAmountSat, payerAmountSat) || other.payerAmountSat == payerAmountSat)); + } + + @override + int get hashCode => Object.hash(runtimeType, payerAmountSat); + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ReceiveAmount_BitcoinImplCopyWith<_$ReceiveAmount_BitcoinImpl> get copyWith => + __$$ReceiveAmount_BitcoinImplCopyWithImpl<_$ReceiveAmount_BitcoinImpl>(this, _$identity); +} + +abstract class ReceiveAmount_Bitcoin extends ReceiveAmount { + const factory ReceiveAmount_Bitcoin({required final BigInt payerAmountSat}) = _$ReceiveAmount_BitcoinImpl; + const ReceiveAmount_Bitcoin._() : super._(); + + BigInt get payerAmountSat; + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ReceiveAmount_BitcoinImplCopyWith<_$ReceiveAmount_BitcoinImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ReceiveAmount_AssetImplCopyWith<$Res> { + factory _$$ReceiveAmount_AssetImplCopyWith( + _$ReceiveAmount_AssetImpl value, $Res Function(_$ReceiveAmount_AssetImpl) then) = + __$$ReceiveAmount_AssetImplCopyWithImpl<$Res>; + @useResult + $Res call({String assetId, BigInt? payerAmount}); +} + +/// @nodoc +class __$$ReceiveAmount_AssetImplCopyWithImpl<$Res> + extends _$ReceiveAmountCopyWithImpl<$Res, _$ReceiveAmount_AssetImpl> + implements _$$ReceiveAmount_AssetImplCopyWith<$Res> { + __$$ReceiveAmount_AssetImplCopyWithImpl( + _$ReceiveAmount_AssetImpl _value, $Res Function(_$ReceiveAmount_AssetImpl) _then) + : super(_value, _then); + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? assetId = null, + Object? payerAmount = freezed, + }) { + return _then(_$ReceiveAmount_AssetImpl( + assetId: null == assetId + ? _value.assetId + : assetId // ignore: cast_nullable_to_non_nullable + as String, + payerAmount: freezed == payerAmount + ? _value.payerAmount + : payerAmount // ignore: cast_nullable_to_non_nullable + as BigInt?, + )); + } +} + +/// @nodoc + +class _$ReceiveAmount_AssetImpl extends ReceiveAmount_Asset { + const _$ReceiveAmount_AssetImpl({required this.assetId, this.payerAmount}) : super._(); + + @override + final String assetId; + @override + final BigInt? payerAmount; + + @override + String toString() { + return 'ReceiveAmount.asset(assetId: $assetId, payerAmount: $payerAmount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ReceiveAmount_AssetImpl && + (identical(other.assetId, assetId) || other.assetId == assetId) && + (identical(other.payerAmount, payerAmount) || other.payerAmount == payerAmount)); + } + + @override + int get hashCode => Object.hash(runtimeType, assetId, payerAmount); + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ReceiveAmount_AssetImplCopyWith<_$ReceiveAmount_AssetImpl> get copyWith => + __$$ReceiveAmount_AssetImplCopyWithImpl<_$ReceiveAmount_AssetImpl>(this, _$identity); +} + +abstract class ReceiveAmount_Asset extends ReceiveAmount { + const factory ReceiveAmount_Asset({required final String assetId, final BigInt? payerAmount}) = + _$ReceiveAmount_AssetImpl; + const ReceiveAmount_Asset._() : super._(); + + String get assetId; + BigInt? get payerAmount; + + /// Create a copy of ReceiveAmount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ReceiveAmount_AssetImplCopyWith<_$ReceiveAmount_AssetImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$SdkEvent {} diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index c6f76f264..4de590a59 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -1338,6 +1338,17 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_requestPtr .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_receive_amount() { + return _frbgen_breez_liquid_cst_new_box_autoadd_receive_amount(); + } + + late final _frbgen_breez_liquid_cst_new_box_autoadd_receive_amountPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_receive_amount'); + late final _frbgen_breez_liquid_cst_new_box_autoadd_receive_amount = + _frbgen_breez_liquid_cst_new_box_autoadd_receive_amountPtr + .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request() { return _frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request(); @@ -1493,6 +1504,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 +4182,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; } @@ -4523,13 +4551,22 @@ final class wire_cst_prepare_ln_url_pay_request extends ffi.Struct { external ffi.Pointer validate_success_action_url; } -final class wire_cst_PayAmount_Receiver extends ffi.Struct { +final class wire_cst_PayAmount_Bitcoin extends ffi.Struct { @ffi.Uint64() - external int amount_sat; + external int receiver_amount_sat; +} + +final class wire_cst_PayAmount_Asset extends ffi.Struct { + external ffi.Pointer asset_id; + + @ffi.Uint64() + external int receiver_amount; } final class PayAmountKind extends ffi.Union { - external wire_cst_PayAmount_Receiver Receiver; + external wire_cst_PayAmount_Bitcoin Bitcoin; + + external wire_cst_PayAmount_Asset Asset; } final class wire_cst_pay_amount extends ffi.Struct { @@ -4545,11 +4582,35 @@ final class wire_cst_prepare_pay_onchain_request extends ffi.Struct { external ffi.Pointer fee_rate_sat_per_vbyte; } -final class wire_cst_prepare_receive_request extends ffi.Struct { - external ffi.Pointer payer_amount_sat; +final class wire_cst_ReceiveAmount_Bitcoin extends ffi.Struct { + @ffi.Uint64() + external int payer_amount_sat; +} + +final class wire_cst_ReceiveAmount_Asset extends ffi.Struct { + external ffi.Pointer asset_id; + external ffi.Pointer payer_amount; +} + +final class ReceiveAmountKind extends ffi.Union { + external wire_cst_ReceiveAmount_Bitcoin Bitcoin; + + external wire_cst_ReceiveAmount_Asset Asset; +} + +final class wire_cst_receive_amount extends ffi.Struct { + @ffi.Int32() + external int tag; + + external ReceiveAmountKind kind; +} + +final class wire_cst_prepare_receive_request extends ffi.Struct { @ffi.Int32() external int payment_method; + + external ffi.Pointer amount; } final class wire_cst_prepare_refund_request extends ffi.Struct { @@ -4571,7 +4632,7 @@ final class wire_cst_prepare_receive_response extends ffi.Struct { @ffi.Int32() external int payment_method; - external ffi.Pointer payer_amount_sat; + external ffi.Pointer amount; @ffi.Uint64() external int fees_sat; @@ -4722,6 +4783,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 { @@ -4929,6 +4992,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; @@ -5051,6 +5128,8 @@ final class wire_cst_wallet_info extends ffi.Struct { external ffi.Pointer fingerprint; external ffi.Pointer pubkey; + + external ffi.Pointer asset_balances; } final class wire_cst_get_info_response 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 0d7a5e1a3..72dc66481 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, @@ -1926,23 +1959,21 @@ fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveR return null } val paymentMethod = prepareReceiveRequest.getString("paymentMethod")?.let { asPaymentMethod(it) }!! - val payerAmountSat = - if (hasNonNullKey( - prepareReceiveRequest, - "payerAmountSat", - ) - ) { - prepareReceiveRequest.getDouble("payerAmountSat").toULong() + val amount = + if (hasNonNullKey(prepareReceiveRequest, "amount")) { + prepareReceiveRequest.getMap("amount")?.let { + asReceiveAmount(it) + } } else { null } - return PrepareReceiveRequest(paymentMethod, payerAmountSat) + return PrepareReceiveRequest(paymentMethod, amount) } fun readableMapOf(prepareReceiveRequest: PrepareReceiveRequest): ReadableMap = readableMapOf( "paymentMethod" to prepareReceiveRequest.paymentMethod.name.lowercase(), - "payerAmountSat" to prepareReceiveRequest.payerAmountSat, + "amount" to prepareReceiveRequest.amount?.let { readableMapOf(it) }, ) fun asPrepareReceiveRequestList(arr: ReadableArray): List { @@ -1969,13 +2000,11 @@ fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiv } val paymentMethod = prepareReceiveResponse.getString("paymentMethod")?.let { asPaymentMethod(it) }!! val feesSat = prepareReceiveResponse.getDouble("feesSat").toULong() - val payerAmountSat = - if (hasNonNullKey( - prepareReceiveResponse, - "payerAmountSat", - ) - ) { - prepareReceiveResponse.getDouble("payerAmountSat").toULong() + val amount = + if (hasNonNullKey(prepareReceiveResponse, "amount")) { + prepareReceiveResponse.getMap("amount")?.let { + asReceiveAmount(it) + } } else { null } @@ -2009,14 +2038,14 @@ fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiv } else { null } - return PrepareReceiveResponse(paymentMethod, feesSat, payerAmountSat, minPayerAmountSat, maxPayerAmountSat, swapperFeerate) + return PrepareReceiveResponse(paymentMethod, feesSat, amount, minPayerAmountSat, maxPayerAmountSat, swapperFeerate) } fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap = readableMapOf( "paymentMethod" to prepareReceiveResponse.paymentMethod.name.lowercase(), "feesSat" to prepareReceiveResponse.feesSat, - "payerAmountSat" to prepareReceiveResponse.payerAmountSat, + "amount" to prepareReceiveResponse.amount?.let { readableMapOf(it) }, "minPayerAmountSat" to prepareReceiveResponse.minPayerAmountSat, "maxPayerAmountSat" to prepareReceiveResponse.maxPayerAmountSat, "swapperFeerate" to prepareReceiveResponse.swapperFeerate, @@ -2730,6 +2759,7 @@ fun asWalletInfo(walletInfo: ReadableMap): WalletInfo? { "pendingReceiveSat", "fingerprint", "pubkey", + "assetBalances", ), ) ) { @@ -2740,7 +2770,8 @@ fun asWalletInfo(walletInfo: ReadableMap): WalletInfo? { val pendingReceiveSat = walletInfo.getDouble("pendingReceiveSat").toULong() val fingerprint = walletInfo.getString("fingerprint")!! val pubkey = walletInfo.getString("pubkey")!! - return WalletInfo(balanceSat, pendingSendSat, pendingReceiveSat, fingerprint, pubkey) + val assetBalances = walletInfo.getArray("assetBalances")?.let { asAssetBalanceList(it) }!! + return WalletInfo(balanceSat, pendingSendSat, pendingReceiveSat, fingerprint, pubkey, assetBalances) } fun readableMapOf(walletInfo: WalletInfo): ReadableMap = @@ -2750,6 +2781,7 @@ fun readableMapOf(walletInfo: WalletInfo): ReadableMap = "pendingReceiveSat" to walletInfo.pendingReceiveSat, "fingerprint" to walletInfo.fingerprint, "pubkey" to walletInfo.pubkey, + "assetBalances" to readableArrayOf(walletInfo.assetBalances), ) fun asWalletInfoList(arr: ReadableArray): List { @@ -3011,11 +3043,12 @@ 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")!! + val address = if (hasNonNullKey(listPaymentDetails, "address")) listPaymentDetails.getString("address") else null return ListPaymentDetails.Bitcoin(address) } return null @@ -3026,6 +3059,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 -> { @@ -3197,9 +3231,14 @@ fun asNetworkList(arr: ReadableArray): List { fun asPayAmount(payAmount: ReadableMap): PayAmount? { val type = payAmount.getString("type") - if (type == "receiver") { - val amountSat = payAmount.getDouble("amountSat").toULong() - return PayAmount.Receiver(amountSat) + if (type == "bitcoin") { + val receiverAmountSat = payAmount.getDouble("receiverAmountSat").toULong() + return PayAmount.Bitcoin(receiverAmountSat) + } + if (type == "asset") { + val assetId = payAmount.getString("assetId")!! + val receiverAmount = payAmount.getDouble("receiverAmount").toULong() + return PayAmount.Asset(assetId, receiverAmount) } if (type == "drain") { return PayAmount.Drain @@ -3210,9 +3249,14 @@ fun asPayAmount(payAmount: ReadableMap): PayAmount? { fun readableMapOf(payAmount: PayAmount): ReadableMap? { val map = Arguments.createMap() when (payAmount) { - is PayAmount.Receiver -> { - pushToMap(map, "type", "receiver") - pushToMap(map, "amountSat", payAmount.amountSat) + is PayAmount.Bitcoin -> { + pushToMap(map, "type", "bitcoin") + pushToMap(map, "receiverAmountSat", payAmount.receiverAmountSat) + } + is PayAmount.Asset -> { + pushToMap(map, "type", "asset") + pushToMap(map, "assetId", payAmount.assetId) + pushToMap(map, "receiverAmount", payAmount.receiverAmount) } is PayAmount.Drain -> { pushToMap(map, "type", "drain") @@ -3289,9 +3333,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")!! @@ -3358,6 +3403,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) } @@ -3424,6 +3470,48 @@ fun asPaymentTypeList(arr: ReadableArray): List { return list } +fun asReceiveAmount(receiveAmount: ReadableMap): ReceiveAmount? { + val type = receiveAmount.getString("type") + + if (type == "bitcoin") { + val payerAmountSat = receiveAmount.getDouble("payerAmountSat").toULong() + return ReceiveAmount.Bitcoin(payerAmountSat) + } + if (type == "asset") { + val assetId = receiveAmount.getString("assetId")!! + val payerAmount = if (hasNonNullKey(receiveAmount, "payerAmount")) receiveAmount.getDouble("payerAmount").toULong() else null + return ReceiveAmount.Asset(assetId, payerAmount) + } + return null +} + +fun readableMapOf(receiveAmount: ReceiveAmount): ReadableMap? { + val map = Arguments.createMap() + when (receiveAmount) { + is ReceiveAmount.Bitcoin -> { + pushToMap(map, "type", "bitcoin") + pushToMap(map, "payerAmountSat", receiveAmount.payerAmountSat) + } + is ReceiveAmount.Asset -> { + pushToMap(map, "type", "asset") + pushToMap(map, "assetId", receiveAmount.assetId) + pushToMap(map, "payerAmount", receiveAmount.payerAmount) + } + } + return map +} + +fun asReceiveAmountList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asReceiveAmount(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asSdkEvent(sdkEvent: ReadableMap): SdkEvent? { val type = sdkEvent.getString("type") @@ -3686,6 +3774,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/example/App.js b/packages/react-native/example/App.js index 2e0d13156..4bf8a55db 100644 --- a/packages/react-native/example/App.js +++ b/packages/react-native/example/App.js @@ -18,6 +18,7 @@ import { removeEventListener, prepareReceivePayment, prepareSendPayment, + ReceiveAmountVariant, receivePayment, sendPayment, PaymentMethod @@ -84,8 +85,8 @@ const App = () => { addLine("addEventListener", listenerId) /* Receive lightning payment */ - - let prepareReceiveRes = await prepareReceivePayment({ payerAmountSat: 1000, paymentMethod: PaymentMethod.LIGHTNING }) + let amount = { type: ReceiveAmountVariant.BITCOIN, payerAmountSat: 1000 } + let prepareReceiveRes = await prepareReceivePayment({ amount, paymentMethod: PaymentMethod.LIGHTNING }) addLine("prepareReceivePayment", JSON.stringify(prepareReceiveRes)) // Get the fees required for this payment addLine("Payment fees", `${prepareReceiveRes.feesSat}`) diff --git a/packages/react-native/ios/BreezSDKLiquidMapper.swift b/packages/react-native/ios/BreezSDKLiquidMapper.swift index 893276e1c..dbdd31f4f 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") { @@ -2236,21 +2271,18 @@ enum BreezSDKLiquidMapper { } let paymentMethod = try asPaymentMethod(paymentMethod: paymentMethodTmp) - var payerAmountSat: UInt64? - if hasNonNilKey(data: prepareReceiveRequest, key: "payerAmountSat") { - guard let payerAmountSatTmp = prepareReceiveRequest["payerAmountSat"] as? UInt64 else { - throw SdkError.Generic(message: errUnexpectedValue(fieldName: "payerAmountSat")) - } - payerAmountSat = payerAmountSatTmp + var amount: ReceiveAmount? + if let amountTmp = prepareReceiveRequest["amount"] as? [String: Any?] { + amount = try asReceiveAmount(receiveAmount: amountTmp) } - return PrepareReceiveRequest(paymentMethod: paymentMethod, payerAmountSat: payerAmountSat) + return PrepareReceiveRequest(paymentMethod: paymentMethod, amount: amount) } static func dictionaryOf(prepareReceiveRequest: PrepareReceiveRequest) -> [String: Any?] { return [ "paymentMethod": valueOf(paymentMethod: prepareReceiveRequest.paymentMethod), - "payerAmountSat": prepareReceiveRequest.payerAmountSat == nil ? nil : prepareReceiveRequest.payerAmountSat, + "amount": prepareReceiveRequest.amount == nil ? nil : dictionaryOf(receiveAmount: prepareReceiveRequest.amount!), ] } @@ -2280,13 +2312,11 @@ enum BreezSDKLiquidMapper { guard let feesSat = prepareReceiveResponse["feesSat"] as? UInt64 else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PrepareReceiveResponse")) } - var payerAmountSat: UInt64? - if hasNonNilKey(data: prepareReceiveResponse, key: "payerAmountSat") { - guard let payerAmountSatTmp = prepareReceiveResponse["payerAmountSat"] as? UInt64 else { - throw SdkError.Generic(message: errUnexpectedValue(fieldName: "payerAmountSat")) - } - payerAmountSat = payerAmountSatTmp + var amount: ReceiveAmount? + if let amountTmp = prepareReceiveResponse["amount"] as? [String: Any?] { + amount = try asReceiveAmount(receiveAmount: amountTmp) } + var minPayerAmountSat: UInt64? if hasNonNilKey(data: prepareReceiveResponse, key: "minPayerAmountSat") { guard let minPayerAmountSatTmp = prepareReceiveResponse["minPayerAmountSat"] as? UInt64 else { @@ -2309,14 +2339,14 @@ enum BreezSDKLiquidMapper { swapperFeerate = swapperFeerateTmp } - return PrepareReceiveResponse(paymentMethod: paymentMethod, feesSat: feesSat, payerAmountSat: payerAmountSat, minPayerAmountSat: minPayerAmountSat, maxPayerAmountSat: maxPayerAmountSat, swapperFeerate: swapperFeerate) + return PrepareReceiveResponse(paymentMethod: paymentMethod, feesSat: feesSat, amount: amount, minPayerAmountSat: minPayerAmountSat, maxPayerAmountSat: maxPayerAmountSat, swapperFeerate: swapperFeerate) } static func dictionaryOf(prepareReceiveResponse: PrepareReceiveResponse) -> [String: Any?] { return [ "paymentMethod": valueOf(paymentMethod: prepareReceiveResponse.paymentMethod), "feesSat": prepareReceiveResponse.feesSat, - "payerAmountSat": prepareReceiveResponse.payerAmountSat == nil ? nil : prepareReceiveResponse.payerAmountSat, + "amount": prepareReceiveResponse.amount == nil ? nil : dictionaryOf(receiveAmount: prepareReceiveResponse.amount!), "minPayerAmountSat": prepareReceiveResponse.minPayerAmountSat == nil ? nil : prepareReceiveResponse.minPayerAmountSat, "maxPayerAmountSat": prepareReceiveResponse.maxPayerAmountSat == nil ? nil : prepareReceiveResponse.maxPayerAmountSat, "swapperFeerate": prepareReceiveResponse.swapperFeerate == nil ? nil : prepareReceiveResponse.swapperFeerate, @@ -3136,8 +3166,12 @@ enum BreezSDKLiquidMapper { guard let pubkey = walletInfo["pubkey"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "pubkey", typeName: "WalletInfo")) } + guard let assetBalancesTmp = walletInfo["assetBalances"] as? [[String: Any?]] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "assetBalances", typeName: "WalletInfo")) + } + let assetBalances = try asAssetBalanceList(arr: assetBalancesTmp) - return WalletInfo(balanceSat: balanceSat, pendingSendSat: pendingSendSat, pendingReceiveSat: pendingReceiveSat, fingerprint: fingerprint, pubkey: pubkey) + return WalletInfo(balanceSat: balanceSat, pendingSendSat: pendingSendSat, pendingReceiveSat: pendingReceiveSat, fingerprint: fingerprint, pubkey: pubkey, assetBalances: assetBalances) } static func dictionaryOf(walletInfo: WalletInfo) -> [String: Any?] { @@ -3147,6 +3181,7 @@ enum BreezSDKLiquidMapper { "pendingReceiveSat": walletInfo.pendingReceiveSat, "fingerprint": walletInfo.fingerprint, "pubkey": walletInfo.pubkey, + "assetBalances": arrayOf(assetBalanceList: walletInfo.assetBalances), ] } @@ -3582,15 +3617,15 @@ 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 { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "address", typeName: "ListPaymentDetails")) - } + let _address = listPaymentDetails["address"] as? String + return ListPaymentDetails.bitcoin(address: _address) } @@ -3600,11 +3635,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( @@ -3612,7 +3648,7 @@ enum BreezSDKLiquidMapper { ): return [ "type": "bitcoin", - "address": address, + "address": address == nil ? nil : address, ] } } @@ -3888,11 +3924,20 @@ enum BreezSDKLiquidMapper { static func asPayAmount(payAmount: [String: Any?]) throws -> PayAmount { let type = payAmount["type"] as! String - if type == "receiver" { - guard let _amountSat = payAmount["amountSat"] as? UInt64 else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amountSat", typeName: "PayAmount")) + if type == "bitcoin" { + guard let _receiverAmountSat = payAmount["receiverAmountSat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "receiverAmountSat", typeName: "PayAmount")) } - return PayAmount.receiver(amountSat: _amountSat) + return PayAmount.bitcoin(receiverAmountSat: _receiverAmountSat) + } + if type == "asset" { + guard let _assetId = payAmount["assetId"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "assetId", typeName: "PayAmount")) + } + guard let _receiverAmount = payAmount["receiverAmount"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "receiverAmount", typeName: "PayAmount")) + } + return PayAmount.asset(assetId: _assetId, receiverAmount: _receiverAmount) } if type == "drain" { return PayAmount.drain @@ -3903,12 +3948,21 @@ enum BreezSDKLiquidMapper { static func dictionaryOf(payAmount: PayAmount) -> [String: Any?] { switch payAmount { - case let .receiver( - amountSat + case let .bitcoin( + receiverAmountSat + ): + return [ + "type": "bitcoin", + "receiverAmountSat": receiverAmountSat, + ] + + case let .asset( + assetId, receiverAmount ): return [ - "type": "receiver", - "amountSat": amountSat, + "type": "asset", + "assetId": assetId, + "receiverAmount": receiverAmount, ] case .drain: @@ -3969,13 +4023,16 @@ enum BreezSDKLiquidMapper { return PaymentDetails.lightning(swapId: _swapId, description: _description, liquidExpirationBlockheight: _liquidExpirationBlockheight, preimage: _preimage, invoice: _invoice, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, destinationPubkey: _destinationPubkey, 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 { @@ -4019,10 +4076,11 @@ enum BreezSDKLiquidMapper { ] case let .liquid( - destination, description + assetId, destination, description ): return [ "type": "liquid", + "assetId": assetId, "destination": destination, "description": description, ] @@ -4218,6 +4276,64 @@ enum BreezSDKLiquidMapper { return list } + static func asReceiveAmount(receiveAmount: [String: Any?]) throws -> ReceiveAmount { + let type = receiveAmount["type"] as! String + if type == "bitcoin" { + guard let _payerAmountSat = receiveAmount["payerAmountSat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "payerAmountSat", typeName: "ReceiveAmount")) + } + return ReceiveAmount.bitcoin(payerAmountSat: _payerAmountSat) + } + if type == "asset" { + guard let _assetId = receiveAmount["assetId"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "assetId", typeName: "ReceiveAmount")) + } + let _payerAmount = receiveAmount["payerAmount"] as? UInt64 + + return ReceiveAmount.asset(assetId: _assetId, payerAmount: _payerAmount) + } + + throw SdkError.Generic(message: "Unexpected type \(type) for enum ReceiveAmount") + } + + static func dictionaryOf(receiveAmount: ReceiveAmount) -> [String: Any?] { + switch receiveAmount { + case let .bitcoin( + payerAmountSat + ): + return [ + "type": "bitcoin", + "payerAmountSat": payerAmountSat, + ] + + case let .asset( + assetId, payerAmount + ): + return [ + "type": "asset", + "assetId": assetId, + "payerAmount": payerAmount == nil ? nil : payerAmount, + ] + } + } + + static func arrayOf(receiveAmountList: [ReceiveAmount]) -> [Any] { + return receiveAmountList.map { v -> [String: Any?] in return dictionaryOf(receiveAmount: v) } + } + + static func asReceiveAmountList(arr: [Any]) throws -> [ReceiveAmount] { + var list = [ReceiveAmount]() + for value in arr { + if let val = value as? [String: Any?] { + var receiveAmount = try asReceiveAmount(receiveAmount: val) + list.append(receiveAmount) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "ReceiveAmount")) + } + } + return list + } + static func asSdkEvent(sdkEvent: [String: Any?]) throws -> SdkEvent { let type = sdkEvent["type"] as! String if type == "paymentFailed" { diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 34e817dd5..7821f230b 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 } @@ -336,13 +341,13 @@ export interface PreparePayOnchainResponse { export interface PrepareReceiveRequest { paymentMethod: PaymentMethod - payerAmountSat?: number + amount?: ReceiveAmount } export interface PrepareReceiveResponse { paymentMethod: PaymentMethod feesSat: number - payerAmountSat?: number + amount?: ReceiveAmount minPayerAmountSat?: number maxPayerAmountSat?: number swapperFeerate?: number @@ -462,6 +467,7 @@ export interface WalletInfo { pendingReceiveSat: number fingerprint: string pubkey: string + assetBalances: AssetBalance[] } export enum AesSuccessActionDataResultVariant { @@ -561,10 +567,11 @@ export enum ListPaymentDetailsVariant { export type ListPaymentDetails = { type: ListPaymentDetailsVariant.LIQUID, - destination: string + assetId?: string + destination?: string } | { type: ListPaymentDetailsVariant.BITCOIN, - address: string + address?: string } export enum LnUrlCallbackStatusVariant { @@ -621,13 +628,18 @@ export enum Network { } export enum PayAmountVariant { - RECEIVER = "receiver", + BITCOIN = "bitcoin", + ASSET = "asset", DRAIN = "drain" } export type PayAmount = { - type: PayAmountVariant.RECEIVER, - amountSat: number + type: PayAmountVariant.BITCOIN, + receiverAmountSat: number +} | { + type: PayAmountVariant.ASSET, + assetId: string + receiverAmount: number } | { type: PayAmountVariant.DRAIN } @@ -653,6 +665,7 @@ export type PaymentDetails = { refundTxAmountSat?: number } | { type: PaymentDetailsVariant.LIQUID, + assetId: string destination: string description: string } | { @@ -687,6 +700,20 @@ export enum PaymentType { SEND = "send" } +export enum ReceiveAmountVariant { + BITCOIN = "bitcoin", + ASSET = "asset" +} + +export type ReceiveAmount = { + type: ReceiveAmountVariant.BITCOIN, + payerAmountSat: number +} | { + type: ReceiveAmountVariant.ASSET, + assetId: string + payerAmount?: number +} + export enum SdkEventVariant { PAYMENT_FAILED = "paymentFailed", PAYMENT_PENDING = "paymentPending",