Expose fees for review + auto accept

This commit is contained in:
Daniel Granhão
2024-12-20 13:36:30 +00:00
parent 08f0fd26cc
commit 351473a41e
30 changed files with 1195 additions and 367 deletions

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
use std::thread;
use std::time::Duration;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use breez_sdk_liquid::prelude::*;
use clap::{arg, Parser};
use qrcode_rs::render::unicode;
@@ -131,6 +131,14 @@ pub(crate) enum Command {
/// Lightning payment hash
payment_hash: String,
},
/// Get proposed fees for WaitingFeeAcceptance Payment
FetchPaymentProposedFees { swap_id: String },
/// Accept proposed fees for WaitingFeeAcceptance Payment
AcceptPaymentProposedFees {
swap_id: String,
// Fee amount obtained using FetchPaymentProposedFees
fees_sat: u64,
},
/// List refundable chain swaps
ListRefundables,
/// Prepare a refund transaction for an incomplete swap
@@ -519,6 +527,23 @@ pub(crate) async fn handle_command(
}
}
}
Command::FetchPaymentProposedFees { swap_id } => {
let res = sdk
.fetch_payment_proposed_fees(&FetchPaymentProposedFeesRequest { swap_id })
.await?;
command_result!(res)
}
Command::AcceptPaymentProposedFees { swap_id, fees_sat } => {
let res = sdk
.fetch_payment_proposed_fees(&FetchPaymentProposedFeesRequest { swap_id })
.await?;
if fees_sat != res.fees_sat {
bail!("Fees changed since they were fetched")
}
sdk.accept_payment_proposed_fees(&AcceptPaymentProposedFeesRequest { response: res })
.await?;
command_result!("Proposed fees accepted successfully")
}
Command::ListRefundables => {
let refundables = sdk.list_refundables().await?;
command_result!(refundables)

View File

@@ -16,6 +16,8 @@ typedef struct _Dart_Handle* Dart_Handle;
#define ESTIMATED_BTC_CLAIM_TX_VSIZE 111
#define ESTIMATED_BTC_LOCKUP_TX_VSIZE 154
#define STANDARD_FEE_RATE_SAT_PER_VBYTE 0.1
#define LOWBALL_FEE_RATE_SAT_PER_VBYTE 0.01
@@ -541,6 +543,10 @@ typedef struct wire_cst_SdkEvent_PaymentWaitingConfirmation {
struct wire_cst_payment *details;
} wire_cst_SdkEvent_PaymentWaitingConfirmation;
typedef struct wire_cst_SdkEvent_PaymentWaitingFeeAcceptance {
struct wire_cst_payment *details;
} wire_cst_SdkEvent_PaymentWaitingFeeAcceptance;
typedef union SdkEventKind {
struct wire_cst_SdkEvent_PaymentFailed PaymentFailed;
struct wire_cst_SdkEvent_PaymentPending PaymentPending;
@@ -548,6 +554,7 @@ typedef union SdkEventKind {
struct wire_cst_SdkEvent_PaymentRefundPending PaymentRefundPending;
struct wire_cst_SdkEvent_PaymentSucceeded PaymentSucceeded;
struct wire_cst_SdkEvent_PaymentWaitingConfirmation PaymentWaitingConfirmation;
struct wire_cst_SdkEvent_PaymentWaitingFeeAcceptance PaymentWaitingFeeAcceptance;
} SdkEventKind;
typedef struct wire_cst_sdk_event {
@@ -580,6 +587,7 @@ typedef struct wire_cst_config {
struct wire_cst_list_prim_u_8_strict *breez_api_key;
struct wire_cst_list_external_input_parser *external_input_parsers;
bool use_default_external_input_parsers;
uint32_t *onchain_fee_rate_leeway_sat_per_vbyte;
} wire_cst_config;
typedef struct wire_cst_connect_request {

View File

@@ -338,7 +338,7 @@ dictionary Config {
u64? zero_conf_max_amount_sat;
boolean use_default_external_input_parsers = true;
sequence<ExternalInputParser>? external_input_parsers = null;
u32? onchain_fee_rate_leeway_sat_per_vbyte;
u32? onchain_fee_rate_leeway_sat_per_vbyte = null;
};
enum LiquidNetwork {
@@ -599,7 +599,7 @@ enum PaymentState {
"TimedOut",
"Refundable",
"RefundPending",
"WaitingUserAction",
"WaitingFeeAcceptance",
};
dictionary RefundableSwap {
@@ -775,7 +775,7 @@ interface BindingLiquidSdk {
[Throws=SdkError]
FetchPaymentProposedFeesResponse fetch_payment_proposed_fees(FetchPaymentProposedFeesRequest req);
[Throws=SdkError]
[Throws=PaymentError]
void accept_payment_proposed_fees(AcceptPaymentProposedFeesRequest req);
[Throws=SdkError]

View File

@@ -170,6 +170,20 @@ impl BindingLiquidSdk {
rt().block_on(self.sdk.get_payment(&req))
}
pub fn fetch_payment_proposed_fees(
&self,
req: FetchPaymentProposedFeesRequest,
) -> SdkResult<FetchPaymentProposedFeesResponse> {
rt().block_on(self.sdk.fetch_payment_proposed_fees(&req))
}
pub fn accept_payment_proposed_fees(
&self,
req: AcceptPaymentProposedFeesRequest,
) -> Result<(), PaymentError> {
rt().block_on(self.sdk.accept_payment_proposed_fees(&req))
}
pub fn prepare_lnurl_pay(
&self,
req: PrepareLnUrlPayRequest,

View File

@@ -32,8 +32,9 @@ use crate::{
wallet::OnchainWallet,
};
// Estimates based on https://github.com/BoltzExchange/boltz-backend/blob/ee4c77be1fcb9bb2b45703c542ad67f7efbf218d/lib/rates/FeeProvider.ts#L78
// Estimates based on https://github.com/BoltzExchange/boltz-backend/blob/ee4c77be1fcb9bb2b45703c542ad67f7efbf218d/lib/rates/FeeProvider.ts#L68
pub const ESTIMATED_BTC_CLAIM_TX_VSIZE: u64 = 111;
pub const ESTIMATED_BTC_LOCKUP_TX_VSIZE: u64 = 154;
pub(crate) struct ChainSwapHandler {
config: Config,
@@ -327,12 +328,12 @@ impl ChainSwapHandler {
| ChainSwapStates::TransactionRefunded
| ChainSwapStates::SwapExpired => {
// Zero-amount Receive Chain Swaps also get to TransactionLockupFailed when user locks up funds
let is_zero_amount = swap.payer_amount_sat == 0;
let is_zero_amount = swap.get_boltz_create_response()?.lockup_details.amount == 0;
if matches!(swap_state, ChainSwapStates::TransactionLockupFailed) && is_zero_amount
{
match self.handle_amountless_update(swap).await {
Ok(_) => {
// We successfully accepted the quote, the swap should continue as normal
// Either we accepted the quote, or we will be waiting for user fee acceptance
return Ok(()); // Break from TxLockupFailed branch
}
// In case of error, we continue and mark it as refundable
@@ -383,17 +384,43 @@ impl ChainSwapHandler {
.map(|quote| quote.to_sat())?;
info!("Got quote of {quote} sat for swap {}", &swap.id);
self.validate_and_update_amountless_swap(swap, quote)
.await?;
match self.validate_amountless_swap(swap, quote).await? {
ValidateAmountlessSwapResult::ReadyForAccepting {
user_lockup_amount_sat,
receiver_amount_sat,
} => {
debug!("Zero-amount swap validated. Auto-accepting...");
self.persister.update_zero_amount_swap_values(
&swap.id,
user_lockup_amount_sat,
receiver_amount_sat,
)?;
self.swapper
.accept_zero_amount_chain_swap_quote(&swap.id, quote)
.map_err(Into::into)
}
ValidateAmountlessSwapResult::RequiresUserAction {
user_lockup_amount_sat,
receiver_amount_sat_original_estimate,
} => {
debug!("Zero-amount swap validated. Fees are too high for automatic accepting. Moving to WaitingFeeAcceptance");
// While the user doesn't accept new fees, let's continue to show the original estimate
self.persister.update_zero_amount_swap_values(
&swap.id,
user_lockup_amount_sat,
receiver_amount_sat_original_estimate,
)?;
self.update_swap_info(&swap.id, WaitingFeeAcceptance, None, None, None, None)
.await
}
}
}
async fn validate_and_update_amountless_swap(
async fn validate_amountless_swap(
&self,
swap: &ChainSwap,
quote_server_lockup_amount_sat: u64,
) -> Result<(), PaymentError> {
) -> Result<ValidateAmountlessSwapResult, PaymentError> {
debug!("Validating {swap:?}");
ensure_sdk!(
@@ -425,32 +452,45 @@ impl ChainSwapHandler {
);
let pair = swap.get_boltz_pair()?;
let swapper_service_feerate = pair.fees.percentage;
let swapper_server_fees_sat = pair.fees.server();
let service_fees_sat =
((swapper_service_feerate / 100.0) * user_lockup_amount_sat as f64).ceil() as u64;
let fees_sat = swapper_server_fees_sat + service_fees_sat;
ensure_sdk!(
user_lockup_amount_sat > fees_sat,
PaymentError::generic(&format!("Invalid quote: fees ({fees_sat} sat) are higher than user lockup ({user_lockup_amount_sat} sat)"))
);
let expected_server_lockup_amount_sat = user_lockup_amount_sat - fees_sat;
debug!("user_lockup_amount_sat = {}, service_fees_sat = {}, server_fees_sat = {}, expected_server_lockup_amount_sat = {}, quote_server_lockup_amount_sat = {}",
user_lockup_amount_sat, service_fees_sat, swapper_server_fees_sat, expected_server_lockup_amount_sat, quote_server_lockup_amount_sat);
ensure_sdk!(
expected_server_lockup_amount_sat <= quote_server_lockup_amount_sat,
PaymentError::generic(&format!("Invalid quote: expected at least {expected_server_lockup_amount_sat} sat, got {quote_server_lockup_amount_sat} sat"))
// Original server lockup quote estimate
let server_fees_estimate_sat = pair.fees.server();
let service_fees_sat = pair.fees.boltz(user_lockup_amount_sat);
let server_lockup_amount_estimate_sat =
user_lockup_amount_sat - server_fees_estimate_sat - service_fees_sat;
// Min auto accept server lockup quote
let server_fees_leeway_sat = self
.config
.onchain_fee_rate_leeway_sat_per_vbyte
.unwrap_or(0) as u64
* ESTIMATED_BTC_LOCKUP_TX_VSIZE;
let min_auto_accept_server_lockup_amount_sat =
server_lockup_amount_estimate_sat.saturating_sub(server_fees_leeway_sat);
debug!(
"user_lockup_amount_sat = {user_lockup_amount_sat}, \
service_fees_sat = {service_fees_sat}, \
server_fees_estimate_sat = {server_fees_estimate_sat}, \
server_fees_leeway_sat = {server_fees_leeway_sat}, \
min_auto_accept_server_lockup_amount_sat = {min_auto_accept_server_lockup_amount_sat}, \
quote_server_lockup_amount_sat = {quote_server_lockup_amount_sat}",
);
if min_auto_accept_server_lockup_amount_sat > quote_server_lockup_amount_sat {
let receiver_amount_sat_original_estimate =
server_lockup_amount_estimate_sat - swap.claim_fees_sat;
Ok(ValidateAmountlessSwapResult::RequiresUserAction {
user_lockup_amount_sat,
receiver_amount_sat_original_estimate,
})
} else {
let receiver_amount_sat = quote_server_lockup_amount_sat - swap.claim_fees_sat;
self.persister.update_zero_amount_swap_values(
&swap.id,
Ok(ValidateAmountlessSwapResult::ReadyForAccepting {
user_lockup_amount_sat,
receiver_amount_sat,
)?;
Ok(())
})
}
}
async fn on_new_outgoing_status(&self, swap: &ChainSwap, update: &boltz::Update) -> Result<()> {
@@ -1077,12 +1117,17 @@ impl ChainSwapHandler {
err: "Cannot transition to Created state".to_string(),
}),
(Created | Pending, Pending) => Ok(()),
(Created | Pending | WaitingFeeAcceptance, Pending) => Ok(()),
(_, Pending) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Pending state"),
}),
(Created | Pending | RefundPending, Complete) => Ok(()),
(Created | Pending | WaitingFeeAcceptance, WaitingFeeAcceptance) => Ok(()),
(_, WaitingFeeAcceptance) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to WaitingFeeAcceptance state"),
}),
(Created | Pending | WaitingFeeAcceptance | RefundPending, Complete) => Ok(()),
(_, Complete) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Complete state"),
}),
@@ -1092,12 +1137,15 @@ impl ChainSwapHandler {
err: format!("Cannot transition from {from_state:?} to TimedOut state"),
}),
(Created | Pending | RefundPending | Failed | Complete, Refundable) => Ok(()),
(
Created | Pending | WaitingFeeAcceptance | RefundPending | Failed | Complete,
Refundable,
) => Ok(()),
(_, Refundable) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Refundable state"),
}),
(Pending | Refundable, RefundPending) => Ok(()),
(Pending | WaitingFeeAcceptance | Refundable, RefundPending) => Ok(()),
(_, RefundPending) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to RefundPending state"),
}),
@@ -1299,6 +1347,17 @@ impl ChainSwapHandler {
}
}
enum ValidateAmountlessSwapResult {
ReadyForAccepting {
user_lockup_amount_sat: u64,
receiver_amount_sat: u64,
},
RequiresUserAction {
user_lockup_amount_sat: u64,
receiver_amount_sat_original_estimate: u64,
},
}
#[cfg(test)]
mod tests {
use anyhow::Result;
@@ -1322,15 +1381,47 @@ mod tests {
let chain_swap_handler = new_chain_swap_handler(persister.clone())?;
// Test valid combinations of states
let all_states = HashSet::from([Created, Pending, Complete, TimedOut, Failed]);
let all_states = HashSet::from([
Created,
Pending,
WaitingFeeAcceptance,
Complete,
TimedOut,
Failed,
]);
let valid_combinations = HashMap::from([
(
Created,
HashSet::from([Pending, Complete, TimedOut, Refundable, Failed]),
HashSet::from([
Pending,
WaitingFeeAcceptance,
Complete,
TimedOut,
Refundable,
Failed,
]),
),
(
Pending,
HashSet::from([Pending, Complete, Refundable, RefundPending, Failed]),
HashSet::from([
Pending,
WaitingFeeAcceptance,
Complete,
Refundable,
RefundPending,
Failed,
]),
),
(
WaitingFeeAcceptance,
HashSet::from([
Pending,
WaitingFeeAcceptance,
Complete,
Refundable,
RefundPending,
Failed,
]),
),
(TimedOut, HashSet::from([Failed])),
(Complete, HashSet::from([Refundable])),
@@ -1342,7 +1433,7 @@ mod tests {
for (first_state, allowed_states) in valid_combinations.iter() {
for allowed_state in allowed_states {
let chain_swap =
new_chain_swap(Direction::Incoming, Some(*first_state), false, None);
new_chain_swap(Direction::Incoming, Some(*first_state), false, None, false);
persister.insert_or_update_chain_swap(&chain_swap)?;
assert!(chain_swap_handler
@@ -1369,7 +1460,7 @@ mod tests {
for (first_state, disallowed_states) in invalid_combinations.iter() {
for disallowed_state in disallowed_states {
let chain_swap =
new_chain_swap(Direction::Incoming, Some(*first_state), false, None);
new_chain_swap(Direction::Incoming, Some(*first_state), false, None, false);
persister.insert_or_update_chain_swap(&chain_swap)?;
assert!(chain_swap_handler

View File

@@ -2092,6 +2092,7 @@ impl CstDecode<crate::model::PaymentState> for i32 {
4 => crate::model::PaymentState::TimedOut,
5 => crate::model::PaymentState::Refundable,
6 => crate::model::PaymentState::RefundPending,
7 => crate::model::PaymentState::WaitingFeeAcceptance,
_ => unreachable!("Invalid variant for PaymentState: {}", self),
}
}
@@ -2378,6 +2379,7 @@ impl SseDecode for crate::model::Config {
let mut var_externalInputParsers =
<Option<Vec<crate::bindings::ExternalInputParser>>>::sse_decode(deserializer);
let mut var_useDefaultExternalInputParsers = <bool>::sse_decode(deserializer);
let mut var_onchainFeeRateLeewaySatPerVbyte = <Option<u32>>::sse_decode(deserializer);
return crate::model::Config {
liquid_electrum_url: var_liquidElectrumUrl,
bitcoin_electrum_url: var_bitcoinElectrumUrl,
@@ -2392,6 +2394,7 @@ impl SseDecode for crate::model::Config {
breez_api_key: var_breezApiKey,
external_input_parsers: var_externalInputParsers,
use_default_external_input_parsers: var_useDefaultExternalInputParsers,
onchain_fee_rate_leeway_sat_per_vbyte: var_onchainFeeRateLeewaySatPerVbyte,
};
}
}
@@ -3781,6 +3784,7 @@ impl SseDecode for crate::model::PaymentState {
4 => crate::model::PaymentState::TimedOut,
5 => crate::model::PaymentState::Refundable,
6 => crate::model::PaymentState::RefundPending,
7 => crate::model::PaymentState::WaitingFeeAcceptance,
_ => unreachable!("Invalid variant for PaymentState: {}", inner),
};
}
@@ -4170,6 +4174,12 @@ impl SseDecode for crate::model::SdkEvent {
};
}
6 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::SdkEvent::PaymentWaitingFeeAcceptance {
details: var_details,
};
}
7 => {
return crate::model::SdkEvent::Synced;
}
_ => {
@@ -4677,6 +4687,9 @@ impl flutter_rust_bridge::IntoDart for crate::model::Config {
self.use_default_external_input_parsers
.into_into_dart()
.into_dart(),
self.onchain_fee_rate_leeway_sat_per_vbyte
.into_into_dart()
.into_dart(),
]
.into_dart()
}
@@ -5836,6 +5849,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentState {
Self::TimedOut => 4.into_dart(),
Self::Refundable => 5.into_dart(),
Self::RefundPending => 6.into_dart(),
Self::WaitingFeeAcceptance => 7.into_dart(),
_ => unreachable!(),
}
}
@@ -6368,7 +6382,10 @@ impl flutter_rust_bridge::IntoDart for crate::model::SdkEvent {
crate::model::SdkEvent::PaymentWaitingConfirmation { details } => {
[5.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::SdkEvent::Synced => [6.into_dart()].into_dart(),
crate::model::SdkEvent::PaymentWaitingFeeAcceptance { details } => {
[6.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::SdkEvent::Synced => [7.into_dart()].into_dart(),
_ => {
unimplemented!("");
}
@@ -6787,6 +6804,7 @@ impl SseEncode for crate::model::Config {
serializer,
);
<bool>::sse_encode(self.use_default_external_input_parsers, serializer);
<Option<u32>>::sse_encode(self.onchain_fee_rate_leeway_sat_per_vbyte, serializer);
}
}
@@ -7907,6 +7925,7 @@ impl SseEncode for crate::model::PaymentState {
crate::model::PaymentState::TimedOut => 4,
crate::model::PaymentState::Refundable => 5,
crate::model::PaymentState::RefundPending => 6,
crate::model::PaymentState::WaitingFeeAcceptance => 7,
_ => {
unimplemented!("");
}
@@ -8181,8 +8200,12 @@ impl SseEncode for crate::model::SdkEvent {
<i32>::sse_encode(5, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::SdkEvent::Synced => {
crate::model::SdkEvent::PaymentWaitingFeeAcceptance { details } => {
<i32>::sse_encode(6, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::SdkEvent::Synced => {
<i32>::sse_encode(7, serializer);
}
_ => {
unimplemented!("");
@@ -8947,6 +8970,9 @@ mod io {
use_default_external_input_parsers: self
.use_default_external_input_parsers
.cst_decode(),
onchain_fee_rate_leeway_sat_per_vbyte: self
.onchain_fee_rate_leeway_sat_per_vbyte
.cst_decode(),
}
}
}
@@ -10152,7 +10178,13 @@ mod io {
details: ans.details.cst_decode(),
}
}
6 => crate::model::SdkEvent::Synced,
6 => {
let ans = unsafe { self.kind.PaymentWaitingFeeAcceptance };
crate::model::SdkEvent::PaymentWaitingFeeAcceptance {
details: ans.details.cst_decode(),
}
}
7 => crate::model::SdkEvent::Synced,
_ => unreachable!(),
}
}
@@ -10437,6 +10469,7 @@ mod io {
breez_api_key: core::ptr::null_mut(),
external_input_parsers: core::ptr::null_mut(),
use_default_external_input_parsers: Default::default(),
onchain_fee_rate_leeway_sat_per_vbyte: core::ptr::null_mut(),
}
}
}
@@ -12517,6 +12550,7 @@ mod io {
breez_api_key: *mut wire_cst_list_prim_u_8_strict,
external_input_parsers: *mut wire_cst_list_external_input_parser,
use_default_external_input_parsers: bool,
onchain_fee_rate_leeway_sat_per_vbyte: *mut u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
@@ -13492,6 +13526,7 @@ mod io {
PaymentRefundPending: wire_cst_SdkEvent_PaymentRefundPending,
PaymentSucceeded: wire_cst_SdkEvent_PaymentSucceeded,
PaymentWaitingConfirmation: wire_cst_SdkEvent_PaymentWaitingConfirmation,
PaymentWaitingFeeAcceptance: wire_cst_SdkEvent_PaymentWaitingFeeAcceptance,
nil__: (),
}
#[repr(C)]
@@ -13526,6 +13561,11 @@ mod io {
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_SdkEvent_PaymentWaitingFeeAcceptance {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_send_destination {
tag: i32,
kind: SendDestinationKind,

View File

@@ -65,6 +65,13 @@ pub struct Config {
/// ([DEFAULT_EXTERNAL_INPUT_PARSERS](crate::sdk::DEFAULT_EXTERNAL_INPUT_PARSERS)).
/// Set this to false in order to prevent their use.
pub use_default_external_input_parsers: bool,
/// For payments where the onchain fees can only be estimated on creation, this can be used
/// in order to automatically allow slightly more expensive fees. If the actual fee rate ends up
/// being above the sum of the initial estimate and this leeway, the payment will require
/// user fee acceptance. See [WaitingFeeAcceptance](PaymentState::WaitingFeeAcceptance).
///
/// Defaults to zero.
pub onchain_fee_rate_leeway_sat_per_vbyte: Option<u32>,
}
impl Config {
@@ -83,6 +90,7 @@ impl Config {
breez_api_key: Some(breez_api_key),
external_input_parsers: None,
use_default_external_input_parsers: true,
onchain_fee_rate_leeway_sat_per_vbyte: None,
}
}
@@ -101,6 +109,7 @@ impl Config {
breez_api_key,
external_input_parsers: None,
use_default_external_input_parsers: true,
onchain_fee_rate_leeway_sat_per_vbyte: None,
}
}
@@ -243,6 +252,7 @@ pub enum SdkEvent {
PaymentRefundPending { details: Payment },
PaymentSucceeded { details: Payment },
PaymentWaitingConfirmation { details: Payment },
PaymentWaitingFeeAcceptance { details: Payment },
Synced,
}
@@ -1121,6 +1131,19 @@ pub enum PaymentState {
///
/// When the refund tx is broadcast, `refund_tx_id` is set in the swap.
RefundPending = 6,
/// ## Chain Swaps
///
/// This is the state when the user needs to accept new fees before the payment can proceed.
///
/// Use [LiquidSdk::fetch_payment_proposed_fees](crate::sdk::LiquidSdk::fetch_payment_proposed_fees)
/// to find out the current fees and
/// [LiquidSdk::accept_payment_proposed_fees](crate::sdk::LiquidSdk::accept_payment_proposed_fees)
/// to accept them, allowing the payment to proceed.
///
/// Otherwise, this payment can be immediately refunded using
/// [prepare_refund](crate::sdk::LiquidSdk::prepare_refund)/[refund](crate::sdk::LiquidSdk::refund).
WaitingFeeAcceptance = 7,
}
impl ToSql for PaymentState {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
@@ -1138,6 +1161,7 @@ impl FromSql for PaymentState {
4 => Ok(PaymentState::TimedOut),
5 => Ok(PaymentState::Refundable),
6 => Ok(PaymentState::RefundPending),
7 => Ok(PaymentState::WaitingFeeAcceptance),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
@@ -1731,6 +1755,27 @@ impl Utxo {
}
}
/// An argument when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
#[derive(Debug, Clone)]
pub struct FetchPaymentProposedFeesRequest {
pub swap_id: String,
}
/// Returned when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
#[derive(Debug, Clone, Serialize)]
pub struct FetchPaymentProposedFeesResponse {
pub swap_id: String,
pub fees_sat: u64,
/// Amount sent by the swap payer
pub payer_amount_sat: u64,
}
/// An argument when calling [crate::sdk::LiquidSdk::accept_payment_proposed_fees].
#[derive(Debug, Clone)]
pub struct AcceptPaymentProposedFeesRequest {
pub response: FetchPaymentProposedFeesResponse,
}
#[macro_export]
macro_rules! get_invoice_amount {
($invoice:expr) => {

View File

@@ -233,13 +233,14 @@ impl Persister {
let where_clause = vec![get_where_clause_state_in(&[
PaymentState::Created,
PaymentState::Pending,
PaymentState::WaitingFeeAcceptance,
])];
self.list_chain_swaps_where(&con, where_clause)
}
pub(crate) fn list_pending_chain_swaps(&self) -> Result<Vec<ChainSwap>> {
self.list_chain_swaps_by_state(vec![PaymentState::Pending, PaymentState::RefundPending])
self.list_chain_swaps_by_state(vec![PaymentState::Pending, PaymentState::RefundPending, PaymentState::WaitingFeeAcceptance])
}
pub(crate) fn list_refundable_chain_swaps(&self) -> Result<Vec<ChainSwap>> {

View File

@@ -405,6 +405,10 @@ impl ReceiveSwapHandler {
err: format!("Cannot transition from {from_state:?} to Failed state"),
}),
(_, Failed) => Ok(()),
(_, WaitingFeeAcceptance) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to WaitingFeeAcceptance state"),
}),
}
}

View File

@@ -3,7 +3,7 @@ use std::ops::Not as _;
use std::time::Instant;
use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration};
use anyhow::{anyhow, Result};
use anyhow::{anyhow, ensure, Result};
use boltz_client::{swaps::boltz::*, util::secrets::Preimage};
use buy::{BuyBitcoinApi, BuyBitcoinService};
use chain::bitcoin::HybridBitcoinChainService;
@@ -560,6 +560,25 @@ impl LiquidSdk {
}
};
}
WaitingFeeAcceptance => {
let swap_id = &payment
.details
.get_swap_id()
.ok_or(anyhow!("Payment WaitingFeeAcceptance must have a swap"))?;
ensure!(
matches!(
self.persister.fetch_swap_by_id(swap_id)?,
Swap::Chain(ChainSwap { .. })
),
"Swap in WaitingFeeAcceptance payment must be chain swap"
);
self.notify_event_listeners(SdkEvent::PaymentWaitingFeeAcceptance {
details: payment,
})
.await?;
}
RefundPending => {
// The swap state has changed to RefundPending
self.notify_event_listeners(SdkEvent::PaymentRefundPending {
@@ -1298,6 +1317,12 @@ impl LiquidSdk {
"Payment has already failed. Please try with another invoice",
))
}
WaitingFeeAcceptance => {
return Err(PaymentError::Generic {
err: "Send swap payment cannot be in state WaitingFeeAcceptance"
.to_string(),
})
}
},
None => {
let keypair = utils::generate_keypair();
@@ -2541,6 +2566,74 @@ impl LiquidSdk {
Ok(self.persister.get_payment_by_request(req)?)
}
/// Fetches an up-to-date fees proposal for a [Payment] that is [WaitingFeeAcceptance].
///
/// Use [LiquidSdk::accept_payment_proposed_fees] to accept the proposed fees and proceed
/// with the payment.
pub async fn fetch_payment_proposed_fees(
&self,
req: &FetchPaymentProposedFeesRequest,
) -> SdkResult<FetchPaymentProposedFeesResponse> {
let chain_swap =
self.persister
.fetch_chain_swap_by_id(&req.swap_id)?
.ok_or(SdkError::Generic {
err: format!("Could not find Swap {}", req.swap_id),
})?;
let server_lockup_quote = self
.swapper
.get_zero_amount_chain_swap_quote(&req.swap_id)?;
let payer_amount_sat = chain_swap.payer_amount_sat;
let fees_sat = payer_amount_sat - server_lockup_quote.to_sat() + chain_swap.claim_fees_sat;
Ok(FetchPaymentProposedFeesResponse {
swap_id: req.swap_id.clone(),
fees_sat,
payer_amount_sat,
})
}
/// Accepts proposed fees for a [Payment] that is [WaitingFeeAcceptance].
///
/// Use [LiquidSdk::fetch_payment_proposed_fees] to get an up-to-date fees proposal.
pub async fn accept_payment_proposed_fees(
&self,
req: &AcceptPaymentProposedFeesRequest,
) -> Result<(), PaymentError> {
let FetchPaymentProposedFeesResponse {
swap_id,
fees_sat,
payer_amount_sat,
} = req.clone().response;
let chain_swap =
self.persister
.fetch_chain_swap_by_id(&swap_id)?
.ok_or(SdkError::Generic {
err: format!("Could not find Swap {}", swap_id),
})?;
let server_lockup_quote = self.swapper.get_zero_amount_chain_swap_quote(&swap_id)?;
ensure_sdk!(
fees_sat == payer_amount_sat - server_lockup_quote.to_sat() + chain_swap.claim_fees_sat,
PaymentError::InvalidOrExpiredFees
);
self.persister.update_zero_amount_swap_values(
&swap_id,
payer_amount_sat,
payer_amount_sat - fees_sat,
)?;
self.swapper
.accept_zero_amount_chain_swap_quote(&swap_id, server_lockup_quote.to_sat())?;
self.chain_swap_handler
.update_swap_info(&swap_id, Pending, None, None, None, None)
.await
}
/// Empties the Liquid Wallet cache for the [Config::network].
pub fn empty_wallet_cache(&self) -> Result<()> {
let mut path = PathBuf::from(self.config.working_dir.clone());
@@ -3029,6 +3122,8 @@ mod tests {
use lwk_wollet::{elements::Txid, hashes::hex::DisplayHex};
use tokio::sync::Mutex;
use crate::chain_swap::ESTIMATED_BTC_LOCKUP_TX_VSIZE;
use crate::test_utils::swapper::ZeroAmountSwapMockConfig;
use crate::{
model::{Direction, PaymentState, Swap},
sdk::LiquidSdk,
@@ -3049,6 +3144,7 @@ mod tests {
accepts_zero_conf: bool,
initial_payment_state: Option<PaymentState>,
user_lockup_tx_id: Option<String>,
zero_amount: bool,
}
impl Default for NewSwapArgs {
@@ -3058,6 +3154,7 @@ mod tests {
initial_payment_state: None,
direction: Direction::Outgoing,
user_lockup_tx_id: None,
zero_amount: false,
}
}
}
@@ -3082,6 +3179,11 @@ mod tests {
self.initial_payment_state = Some(payment_state);
self
}
pub fn set_zero_amount(mut self, zero_amount: bool) -> Self {
self.zero_amount = zero_amount;
self
}
}
macro_rules! trigger_swap_update {
@@ -3101,6 +3203,7 @@ mod tests {
$args.initial_payment_state,
$args.accepts_zero_conf,
$args.user_lockup_tx_id,
$args.zero_amount,
);
$persister.insert_or_update_chain_swap(&swap).unwrap();
Swap::Chain(swap)
@@ -3274,6 +3377,7 @@ mod tests {
status_stream.clone(),
liquid_chain_service.clone(),
bitcoin_chain_service.clone(),
None,
)?);
LiquidSdk::track_swap_updates(&sdk).await;
@@ -3463,4 +3567,139 @@ mod tests {
Ok(())
}
#[tokio::test]
async fn test_zero_amount_chain_swap_zero_leeway() -> Result<()> {
let user_lockup_sat = 50_000;
let (_tmp_dir, persister) = new_persister()?;
let persister = Arc::new(persister);
let swapper = Arc::new(MockSwapper::new());
let status_stream = Arc::new(MockStatusStream::new());
let liquid_chain_service = Arc::new(Mutex::new(MockLiquidChainService::new()));
let bitcoin_chain_service = Arc::new(Mutex::new(MockBitcoinChainService::new()));
let sdk = Arc::new(new_liquid_sdk_with_chain_services(
persister.clone(),
swapper.clone(),
status_stream.clone(),
liquid_chain_service.clone(),
bitcoin_chain_service.clone(),
None,
)?);
LiquidSdk::track_swap_updates(&sdk).await;
// We spawn a new thread since updates can only be sent when called via async runtimes
tokio::spawn(async move {
// Verify that `TransactionLockupFailed` correctly:
// 1. does not affect state when swapper doesn't increase fees
// 2. triggers a change to WaitingFeeAcceptance when there is a fee increase > 0
for fee_increase in [0, 1] {
swapper.set_zero_amount_swap_mock_config(ZeroAmountSwapMockConfig {
user_lockup_sat,
onchain_fee_increase_sat: fee_increase,
});
bitcoin_chain_service
.lock()
.await
.set_script_balance_sat(user_lockup_sat);
let persisted_swap = trigger_swap_update!(
"chain",
NewSwapArgs::default()
.set_direction(Direction::Incoming)
.set_accepts_zero_conf(false)
.set_zero_amount(true),
persister,
status_stream,
ChainSwapStates::TransactionLockupFailed,
None,
None
);
match fee_increase {
0 => {
assert_eq!(persisted_swap.state, PaymentState::Created);
}
1 => {
assert_eq!(persisted_swap.state, PaymentState::WaitingFeeAcceptance);
}
_ => panic!("Unexpected fee_increase"),
}
}
})
.await?;
Ok(())
}
#[tokio::test]
async fn test_zero_amount_chain_swap_with_leeway() -> Result<()> {
let user_lockup_sat = 50_000;
let onchain_fee_rate_leeway_sat_per_vbyte = 5;
let (_tmp_dir, persister) = new_persister()?;
let persister = Arc::new(persister);
let swapper = Arc::new(MockSwapper::new());
let status_stream = Arc::new(MockStatusStream::new());
let liquid_chain_service = Arc::new(Mutex::new(MockLiquidChainService::new()));
let bitcoin_chain_service = Arc::new(Mutex::new(MockBitcoinChainService::new()));
let sdk = Arc::new(new_liquid_sdk_with_chain_services(
persister.clone(),
swapper.clone(),
status_stream.clone(),
liquid_chain_service.clone(),
bitcoin_chain_service.clone(),
Some(onchain_fee_rate_leeway_sat_per_vbyte),
)?);
LiquidSdk::track_swap_updates(&sdk).await;
let max_fee_increase_for_auto_accept_sat =
onchain_fee_rate_leeway_sat_per_vbyte as u64 * ESTIMATED_BTC_LOCKUP_TX_VSIZE;
// We spawn a new thread since updates can only be sent when called via async runtimes
tokio::spawn(async move {
// Verify that `TransactionLockupFailed` correctly:
// 1. does not affect state when swapper increases fee by up to sat/vbyte leeway * tx size
// 2. triggers a change to WaitingFeeAcceptance when it is any higher
for fee_increase in [
max_fee_increase_for_auto_accept_sat,
max_fee_increase_for_auto_accept_sat + 1,
] {
swapper.set_zero_amount_swap_mock_config(ZeroAmountSwapMockConfig {
user_lockup_sat,
onchain_fee_increase_sat: fee_increase,
});
bitcoin_chain_service
.lock()
.await
.set_script_balance_sat(user_lockup_sat);
let persisted_swap = trigger_swap_update!(
"chain",
NewSwapArgs::default()
.set_direction(Direction::Incoming)
.set_accepts_zero_conf(false)
.set_zero_amount(true),
persister,
status_stream,
ChainSwapStates::TransactionLockupFailed,
None,
None
);
match fee_increase {
val if val == max_fee_increase_for_auto_accept_sat => {
assert_eq!(persisted_swap.state, PaymentState::Created);
}
val if val == (max_fee_increase_for_auto_accept_sat + 1) => {
assert_eq!(persisted_swap.state, PaymentState::WaitingFeeAcceptance);
}
_ => panic!("Unexpected fee_increase"),
}
}
})
.await?;
Ok(())
}
}

View File

@@ -592,6 +592,10 @@ impl SendSwapHandler {
err: format!("Cannot transition from {from_state:?} to Failed state"),
}),
(_, Failed) => Ok(()),
(_, WaitingFeeAcceptance) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to WaitingFeeAcceptance state"),
}),
}
}

View File

@@ -180,7 +180,7 @@ impl Swapper for BoltzSwapper {
Ok((pair_outgoing, pair_incoming))
}
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, PaymentError> {
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, SdkError> {
self.client
.get_quote(swap_id)
.map(|r| Amount::from_sat(r.amount))

View File

@@ -45,7 +45,7 @@ pub trait Swapper: Send + Sync {
///
/// If the user locked-up funds in the valid range this will return that amount. In all other
/// cases, this will return an error.
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, PaymentError>;
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, SdkError>;
/// Accept a specific quote for a Zero-Amount Receive Chain Swap
fn accept_zero_amount_chain_swap_quote(

View File

@@ -128,17 +128,26 @@ impl LiquidChainService for MockLiquidChainService {
pub(crate) struct MockBitcoinChainService {
history: Vec<MockHistory>,
script_balance_sat: u64,
}
impl MockBitcoinChainService {
pub(crate) fn new() -> Self {
MockBitcoinChainService { history: vec![] }
MockBitcoinChainService {
history: vec![],
script_balance_sat: 0,
}
}
pub(crate) fn set_history(&mut self, history: Vec<MockHistory>) -> &mut Self {
self.history = history;
self
}
pub(crate) fn set_script_balance_sat(&mut self, script_balance_sat: u64) -> &mut Self {
self.script_balance_sat = script_balance_sat;
self
}
}
#[async_trait]
@@ -208,7 +217,10 @@ impl BitcoinChainService for MockBitcoinChainService {
_script: &boltz_client::bitcoin::Script,
_retries: u64,
) -> Result<electrum_client::GetBalanceRes> {
unimplemented!()
Ok(GetBalanceRes {
confirmed: self.script_balance_sat,
unconfirmed: 0,
})
}
async fn verify_tx(

View File

@@ -49,7 +49,94 @@ pub(crate) fn new_chain_swap(
payment_state: Option<PaymentState>,
accept_zero_conf: bool,
user_lockup_tx_id: Option<String>,
zero_amount: bool,
) -> ChainSwap {
if zero_amount {
if direction == Direction::Outgoing {
panic!("Zero amount swaps must be incoming")
}
return ChainSwap {
id: generate_random_string(4),
direction: Direction::Incoming,
claim_address: None,
lockup_address: "tb1p7cftn5u3ndt8ln0m6hruwyhsz8kc5sxt557ua03qcew0z29u5paqh8f7uu"
.to_string(),
timeout_block_height: 2868778,
preimage: "bbce422d96c0386c3a6c1b1fe11fc7be3fdd871c6855db6ab2e319e96ec19c78"
.to_string(),
description: Some("Bitcoin transfer".to_string()),
create_response_json: r#"{
"claim_details": {
"swapTree": {
"claimLeaf": {
"output": "82012088a914e5ec6c5b814b2d8616c1a0da0acc8b3388cf80d78820e5f32fc89e6947ca08a7855a99ac145f7de599446a0cc0ff4c9aa2694baa1138ac",
"version": 196
},
"refundLeaf": {
"output": "20692bbff63e48c1c05c5efeb7080f7c2416d2f9ecb79d217410eabc125f4d2ff0ad0312a716b1",
"version": 196
}
},
"lockupAddress": "tlq1pq0gfse32q454tmr30t7yl6lx2sv5sswdzh3j0zygz9v5jwwdq6deaec8ntnjq55yrx300u9ts5ykqnfcpuzrypmtda9yuszq0zpl6j8l9tunvqjyrdm3",
"serverPublicKey": "02692bbff63e48c1c05c5efeb7080f7c2416d2f9ecb79d217410eabc125f4d2ff0",
"timeoutBlockHeight": 1484562,
"amount": 0,
"blindingKey": "ebdd91bb06b2282e879256ff1c1a976016a582fea5418188799b1598281b0a5b"
},
"lockup_details": {
"swapTree": {
"claimLeaf": {
"output": "82012088a914e5ec6c5b814b2d8616c1a0da0acc8b3388cf80d7882039688adbf0625672ec56e713e65ce809ee84e96525a13a68fe521588bf41628cac",
"version": 192
},
"refundLeaf": {
"output": "20edf1db3da18ad19962c8dfd7566048c7dc2e11f3d6580cbfed8f9a1321ffe4c7ad032ac62bb1",
"version": 192
}
},
"lockupAddress": "tb1p7cftn5u3ndt8ln0m6hruwyhsz8kc5sxt557ua03qcew0z29u5paqh8f7uu",
"serverPublicKey": "0239688adbf0625672ec56e713e65ce809ee84e96525a13a68fe521588bf41628c",
"timeoutBlockHeight": 2868778,
"amount": 0,
"bip21": "bitcoin:tb1p7cftn5u3ndt8ln0m6hruwyhsz8kc5sxt557ua03qcew0z29u5paqh8f7uu?amount=0.0001836&label=Send%20to%20L-BTC%20address"
}
}"#.to_string(),
claim_private_key: "4b04c3b95570fc48c7f33bc900b801245c2be31b90d41616477574aedc5b9d28"
.to_string(),
refund_private_key: "9e23d322577cfeb2b5490f3f86db58c806004afcb7c88995927bfdfc1c64cd8c"
.to_string(),
payer_amount_sat: 0,
receiver_amount_sat: 0,
claim_fees_sat: 144,
server_lockup_tx_id: None,
user_lockup_tx_id,
claim_tx_id: None,
refund_tx_id: None,
created_at: utils::now(),
state: payment_state.unwrap_or(PaymentState::Created),
accept_zero_conf,
pair_fees_json: r#"{
"hash": "43087e267db95668b9b7c48efcf44d922484870f1bdb8b926e5d6b76bf4d0709",
"rate": 1,
"limits": {
"maximal": 4294967,
"minimal": 10000,
"maximalZeroConf": 0
},
"fees": {
"percentage": 0.1,
"minerFees": {
"server": 100,
"user": {
"claim": 100,
"lockup": 100
}
}
}
}"#
.to_string(),
};
}
match direction {
Direction::Incoming => ChainSwap {
id: generate_random_string(4),

View File

@@ -21,7 +21,7 @@ pub(crate) fn new_receive_swap_handler(persister: Arc<Persister>) -> Result<Rece
let config = Config::testnet(None);
let signer: Arc<Box<dyn Signer>> = Arc::new(Box::new(MockSigner::new()?));
let onchain_wallet = Arc::new(MockWallet::new(signer)?);
let swapper = Arc::new(MockSwapper::new());
let swapper = Arc::new(MockSwapper::default());
let liquid_chain_service = Arc::new(Mutex::new(MockLiquidChainService::new()));
Ok(ReceiveSwapHandler::new(

View File

@@ -40,6 +40,7 @@ pub(crate) fn new_liquid_sdk(
status_stream,
liquid_chain_service,
bitcoin_chain_service,
None,
)
}
@@ -49,6 +50,7 @@ pub(crate) fn new_liquid_sdk_with_chain_services(
status_stream: Arc<MockStatusStream>,
liquid_chain_service: Arc<Mutex<MockLiquidChainService>>,
bitcoin_chain_service: Arc<Mutex<MockBitcoinChainService>>,
onchain_fee_rate_leeway_sat_per_vbyte: Option<u32>,
) -> Result<LiquidSdk> {
let mut config = Config::testnet(None);
config.working_dir = persister
@@ -56,6 +58,7 @@ pub(crate) fn new_liquid_sdk_with_chain_services(
.to_str()
.ok_or(anyhow!("An invalid SDK directory was specified"))?
.to_string();
config.onchain_fee_rate_leeway_sat_per_vbyte = onchain_fee_rate_leeway_sat_per_vbyte;
let signer: Arc<Box<dyn Signer>> = Arc::new(Box::new(MockSigner::new()?));
let onchain_wallet = Arc::new(MockWallet::new(signer.clone())?);

View File

@@ -20,7 +20,7 @@ pub(crate) fn new_send_swap_handler(persister: Arc<Persister>) -> Result<SendSwa
let config = Config::testnet(None);
let signer: Arc<Box<dyn Signer>> = Arc::new(Box::new(MockSigner::new()?));
let onchain_wallet = Arc::new(MockWallet::new(signer)?);
let swapper = Arc::new(MockSwapper::new());
let swapper = Arc::new(MockSwapper::default());
let chain_service = Arc::new(Mutex::new(MockLiquidChainService::new()));
Ok(SendSwapHandler::new(

View File

@@ -11,8 +11,10 @@ use boltz_client::{
Amount, PublicKey,
};
use sdk_common::invoice::parse_invoice;
use std::sync::Mutex;
use crate::{
ensure_sdk,
error::{PaymentError, SdkError},
model::{Direction, SendSwap, Swap, Transaction as SdkTransaction, Utxo},
swapper::Swapper,
@@ -23,7 +25,15 @@ use crate::{
use super::status_stream::MockStatusStream;
#[derive(Default)]
pub struct MockSwapper {}
pub struct ZeroAmountSwapMockConfig {
pub user_lockup_sat: u64,
pub onchain_fee_increase_sat: u64,
}
#[derive(Default)]
pub struct MockSwapper {
zero_amount_swap_mock_config: Mutex<ZeroAmountSwapMockConfig>,
}
impl MockSwapper {
pub(crate) fn new() -> Self {
@@ -60,6 +70,26 @@ impl MockSwapper {
bip21: None,
}
}
pub(crate) fn set_zero_amount_swap_mock_config(&self, config: ZeroAmountSwapMockConfig) {
*self.zero_amount_swap_mock_config.lock().unwrap() = config;
}
fn get_zero_amount_swap_server_lockup_sat(&self) -> u64 {
let zero_amount_swap_mock_config = self.zero_amount_swap_mock_config.lock().unwrap();
let pair = self
.get_chain_pair(Direction::Incoming)
.expect("mock get_chain_pair failed")
.expect("no chainpair in mock");
let fees = pair
.fees
.boltz(zero_amount_swap_mock_config.user_lockup_sat)
+ pair.fees.server()
+ zero_amount_swap_mock_config.onchain_fee_increase_sat;
zero_amount_swap_mock_config.user_lockup_sat - fees
}
}
impl Swapper for MockSwapper {
@@ -314,15 +344,20 @@ impl Swapper for MockSwapper {
unimplemented!()
}
fn get_zero_amount_chain_swap_quote(&self, _swap_id: &str) -> Result<Amount, PaymentError> {
unimplemented!()
fn get_zero_amount_chain_swap_quote(&self, _swap_id: &str) -> Result<Amount, SdkError> {
let server_lockup_amount_sat = self.get_zero_amount_swap_server_lockup_sat();
Ok(Amount::from_sat(server_lockup_amount_sat))
}
fn accept_zero_amount_chain_swap_quote(
&self,
_swap_id: &str,
_server_lockup_sat: u64,
server_lockup_sat: u64,
) -> Result<(), PaymentError> {
unimplemented!()
ensure_sdk!(
server_lockup_sat == self.get_zero_amount_swap_server_lockup_sat(),
PaymentError::InvalidOrExpiredFees
);
Ok(())
}
}

View File

@@ -1476,12 +1476,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_ln_url_error_data(raw);
}
@protected
LnUrlInfo dco_decode_box_autoadd_ln_url_info(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_ln_url_info(raw);
}
@protected
LnUrlPayErrorData dco_decode_box_autoadd_ln_url_pay_error_data(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -1709,11 +1703,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
network: dco_decode_liquid_network(arr[5]),
paymentTimeoutSec: dco_decode_u_64(arr[6]),
zeroConfMinFeeRateMsat: dco_decode_u_32(arr[7]),
syncServiceUrl: dco_decode_String(arr[8]),
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[9]),
breezApiKey: dco_decode_opt_String(arr[10]),
externalInputParsers: dco_decode_opt_list_external_input_parser(arr[11]),
useDefaultExternalInputParsers: dco_decode_bool(arr[12]),
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[8]),
breezApiKey: dco_decode_opt_String(arr[9]),
externalInputParsers: dco_decode_opt_list_external_input_parser(arr[10]),
useDefaultExternalInputParsers: dco_decode_bool(arr[11]),
onchainFeeRateLeewaySatPerVbyte: dco_decode_opt_box_autoadd_u_32(arr[12]),
);
}
@@ -1964,12 +1958,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
List<PaymentState> dco_decode_list_payment_state(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return (raw as List<dynamic>).map(dco_decode_payment_state).toList();
}
@protected
List<PaymentType> dco_decode_list_payment_type(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -1980,15 +1968,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
ListPaymentsRequest dco_decode_list_payments_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
return ListPaymentsRequest(
filters: dco_decode_opt_list_payment_type(arr[0]),
states: dco_decode_opt_list_payment_state(arr[1]),
fromTimestamp: dco_decode_opt_box_autoadd_i_64(arr[2]),
toTimestamp: dco_decode_opt_box_autoadd_i_64(arr[3]),
offset: dco_decode_opt_box_autoadd_u_32(arr[4]),
limit: dco_decode_opt_box_autoadd_u_32(arr[5]),
details: dco_decode_opt_box_autoadd_list_payment_details(arr[6]),
fromTimestamp: dco_decode_opt_box_autoadd_i_64(arr[1]),
toTimestamp: dco_decode_opt_box_autoadd_i_64(arr[2]),
offset: dco_decode_opt_box_autoadd_u_32(arr[3]),
limit: dco_decode_opt_box_autoadd_u_32(arr[4]),
details: dco_decode_opt_box_autoadd_list_payment_details(arr[5]),
);
}
@@ -2129,22 +2116,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
}
@protected
LnUrlInfo dco_decode_ln_url_info(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
return LnUrlInfo(
lnAddress: dco_decode_opt_String(arr[0]),
lnurlPayComment: dco_decode_opt_String(arr[1]),
lnurlPayDomain: dco_decode_opt_String(arr[2]),
lnurlPayMetadata: dco_decode_opt_String(arr[3]),
lnurlPaySuccessAction: dco_decode_opt_box_autoadd_success_action_processed(arr[4]),
lnurlPayUnprocessedSuccessAction: dco_decode_opt_box_autoadd_success_action(arr[5]),
lnurlWithdrawEndpoint: dco_decode_opt_String(arr[6]),
);
}
@protected
LnUrlPayError dco_decode_ln_url_pay_error(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2458,12 +2429,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_box_autoadd_list_payment_details(raw);
}
@protected
LnUrlInfo? dco_decode_opt_box_autoadd_ln_url_info(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null ? null : dco_decode_box_autoadd_ln_url_info(raw);
}
@protected
PayAmount? dco_decode_opt_box_autoadd_pay_amount(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2512,12 +2477,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_list_external_input_parser(raw);
}
@protected
List<PaymentState>? dco_decode_opt_list_payment_state(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null ? null : dco_decode_list_payment_state(raw);
}
@protected
List<PaymentType>? dco_decode_opt_list_payment_type(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2581,9 +2540,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
bolt11: dco_decode_opt_String(raw[4]),
bolt12Offer: dco_decode_opt_String(raw[5]),
paymentHash: dco_decode_opt_String(raw[6]),
lnurlInfo: dco_decode_opt_box_autoadd_ln_url_info(raw[7]),
refundTxId: dco_decode_opt_String(raw[8]),
refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[9]),
refundTxId: dco_decode_opt_String(raw[7]),
refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[8]),
);
case 1:
return PaymentDetails_Liquid(
@@ -2732,13 +2690,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
PrepareLnUrlPayResponse dco_decode_prepare_ln_url_pay_response(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
return PrepareLnUrlPayResponse(
destination: dco_decode_send_destination(arr[0]),
feesSat: dco_decode_u_64(arr[1]),
data: dco_decode_ln_url_pay_request_data(arr[2]),
comment: dco_decode_opt_String(arr[3]),
successAction: dco_decode_opt_box_autoadd_success_action(arr[4]),
successAction: dco_decode_opt_box_autoadd_success_action(arr[2]),
);
}
@@ -3004,6 +2960,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 6:
return SdkEvent_PaymentWaitingFeeAcceptance(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 7:
return SdkEvent_Synced();
default:
throw Exception("unreachable");
@@ -3443,12 +3403,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_ln_url_error_data(deserializer));
}
@protected
LnUrlInfo sse_decode_box_autoadd_ln_url_info(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_ln_url_info(deserializer));
}
@protected
LnUrlPayErrorData sse_decode_box_autoadd_ln_url_pay_error_data(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -3665,11 +3619,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_network = sse_decode_liquid_network(deserializer);
var var_paymentTimeoutSec = sse_decode_u_64(deserializer);
var var_zeroConfMinFeeRateMsat = sse_decode_u_32(deserializer);
var var_syncServiceUrl = sse_decode_String(deserializer);
var var_zeroConfMaxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_breezApiKey = sse_decode_opt_String(deserializer);
var var_externalInputParsers = sse_decode_opt_list_external_input_parser(deserializer);
var var_useDefaultExternalInputParsers = sse_decode_bool(deserializer);
var var_onchainFeeRateLeewaySatPerVbyte = sse_decode_opt_box_autoadd_u_32(deserializer);
return Config(
liquidElectrumUrl: var_liquidElectrumUrl,
bitcoinElectrumUrl: var_bitcoinElectrumUrl,
@@ -3679,11 +3633,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
network: var_network,
paymentTimeoutSec: var_paymentTimeoutSec,
zeroConfMinFeeRateMsat: var_zeroConfMinFeeRateMsat,
syncServiceUrl: var_syncServiceUrl,
zeroConfMaxAmountSat: var_zeroConfMaxAmountSat,
breezApiKey: var_breezApiKey,
externalInputParsers: var_externalInputParsers,
useDefaultExternalInputParsers: var_useDefaultExternalInputParsers);
useDefaultExternalInputParsers: var_useDefaultExternalInputParsers,
onchainFeeRateLeewaySatPerVbyte: var_onchainFeeRateLeewaySatPerVbyte);
}
@protected
@@ -3964,18 +3918,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
List<PaymentState> sse_decode_list_payment_state(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var len_ = sse_decode_i_32(deserializer);
var ans_ = <PaymentState>[];
for (var idx_ = 0; idx_ < len_; ++idx_) {
ans_.add(sse_decode_payment_state(deserializer));
}
return ans_;
}
@protected
List<PaymentType> sse_decode_list_payment_type(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -3992,7 +3934,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
ListPaymentsRequest sse_decode_list_payments_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_filters = sse_decode_opt_list_payment_type(deserializer);
var var_states = sse_decode_opt_list_payment_state(deserializer);
var var_fromTimestamp = sse_decode_opt_box_autoadd_i_64(deserializer);
var var_toTimestamp = sse_decode_opt_box_autoadd_i_64(deserializer);
var var_offset = sse_decode_opt_box_autoadd_u_32(deserializer);
@@ -4000,7 +3941,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_details = sse_decode_opt_box_autoadd_list_payment_details(deserializer);
return ListPaymentsRequest(
filters: var_filters,
states: var_states,
fromTimestamp: var_fromTimestamp,
toTimestamp: var_toTimestamp,
offset: var_offset,
@@ -4175,26 +4115,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return LnUrlErrorData(reason: var_reason);
}
@protected
LnUrlInfo sse_decode_ln_url_info(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_lnAddress = sse_decode_opt_String(deserializer);
var var_lnurlPayComment = sse_decode_opt_String(deserializer);
var var_lnurlPayDomain = sse_decode_opt_String(deserializer);
var var_lnurlPayMetadata = sse_decode_opt_String(deserializer);
var var_lnurlPaySuccessAction = sse_decode_opt_box_autoadd_success_action_processed(deserializer);
var var_lnurlPayUnprocessedSuccessAction = sse_decode_opt_box_autoadd_success_action(deserializer);
var var_lnurlWithdrawEndpoint = sse_decode_opt_String(deserializer);
return LnUrlInfo(
lnAddress: var_lnAddress,
lnurlPayComment: var_lnurlPayComment,
lnurlPayDomain: var_lnurlPayDomain,
lnurlPayMetadata: var_lnurlPayMetadata,
lnurlPaySuccessAction: var_lnurlPaySuccessAction,
lnurlPayUnprocessedSuccessAction: var_lnurlPayUnprocessedSuccessAction,
lnurlWithdrawEndpoint: var_lnurlWithdrawEndpoint);
}
@protected
LnUrlPayError sse_decode_ln_url_pay_error(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4502,17 +4422,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
LnUrlInfo? sse_decode_opt_box_autoadd_ln_url_info(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_box_autoadd_ln_url_info(deserializer));
} else {
return null;
}
}
@protected
PayAmount? sse_decode_opt_box_autoadd_pay_amount(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4601,17 +4510,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
List<PaymentState>? sse_decode_opt_list_payment_state(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_list_payment_state(deserializer));
} else {
return null;
}
}
@protected
List<PaymentType>? sse_decode_opt_list_payment_type(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4686,7 +4584,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_bolt11 = sse_decode_opt_String(deserializer);
var var_bolt12Offer = sse_decode_opt_String(deserializer);
var var_paymentHash = sse_decode_opt_String(deserializer);
var var_lnurlInfo = sse_decode_opt_box_autoadd_ln_url_info(deserializer);
var var_refundTxId = sse_decode_opt_String(deserializer);
var var_refundTxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
return PaymentDetails_Lightning(
@@ -4696,7 +4593,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
bolt11: var_bolt11,
bolt12Offer: var_bolt12Offer,
paymentHash: var_paymentHash,
lnurlInfo: var_lnurlInfo,
refundTxId: var_refundTxId,
refundTxAmountSat: var_refundTxAmountSat);
case 1:
@@ -4839,15 +4735,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_destination = sse_decode_send_destination(deserializer);
var var_feesSat = sse_decode_u_64(deserializer);
var var_data = sse_decode_ln_url_pay_request_data(deserializer);
var var_comment = sse_decode_opt_String(deserializer);
var var_successAction = sse_decode_opt_box_autoadd_success_action(deserializer);
return PrepareLnUrlPayResponse(
destination: var_destination,
feesSat: var_feesSat,
data: var_data,
comment: var_comment,
successAction: var_successAction);
destination: var_destination, feesSat: var_feesSat, successAction: var_successAction);
}
@protected
@@ -5084,6 +4974,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_details = sse_decode_box_autoadd_payment(deserializer);
return SdkEvent_PaymentWaitingConfirmation(details: var_details);
case 6:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return SdkEvent_PaymentWaitingFeeAcceptance(details: var_details);
case 7:
return SdkEvent_Synced();
default:
throw UnimplementedError('');
@@ -5596,12 +5489,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_ln_url_error_data(self, serializer);
}
@protected
void sse_encode_box_autoadd_ln_url_info(LnUrlInfo self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_ln_url_info(self, serializer);
}
@protected
void sse_encode_box_autoadd_ln_url_pay_error_data(LnUrlPayErrorData self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -5821,11 +5708,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_liquid_network(self.network, serializer);
sse_encode_u_64(self.paymentTimeoutSec, serializer);
sse_encode_u_32(self.zeroConfMinFeeRateMsat, serializer);
sse_encode_String(self.syncServiceUrl, serializer);
sse_encode_opt_box_autoadd_u_64(self.zeroConfMaxAmountSat, serializer);
sse_encode_opt_String(self.breezApiKey, serializer);
sse_encode_opt_list_external_input_parser(self.externalInputParsers, serializer);
sse_encode_bool(self.useDefaultExternalInputParsers, serializer);
sse_encode_opt_box_autoadd_u_32(self.onchainFeeRateLeewaySatPerVbyte, serializer);
}
@protected
@@ -6052,15 +5939,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_list_payment_state(List<PaymentState> 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_payment_state(item, serializer);
}
}
@protected
void sse_encode_list_payment_type(List<PaymentType> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -6074,7 +5952,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
void sse_encode_list_payments_request(ListPaymentsRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_opt_list_payment_type(self.filters, serializer);
sse_encode_opt_list_payment_state(self.states, serializer);
sse_encode_opt_box_autoadd_i_64(self.fromTimestamp, serializer);
sse_encode_opt_box_autoadd_i_64(self.toTimestamp, serializer);
sse_encode_opt_box_autoadd_u_32(self.offset, serializer);
@@ -6208,18 +6085,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(self.reason, serializer);
}
@protected
void sse_encode_ln_url_info(LnUrlInfo self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_opt_String(self.lnAddress, serializer);
sse_encode_opt_String(self.lnurlPayComment, serializer);
sse_encode_opt_String(self.lnurlPayDomain, serializer);
sse_encode_opt_String(self.lnurlPayMetadata, serializer);
sse_encode_opt_box_autoadd_success_action_processed(self.lnurlPaySuccessAction, serializer);
sse_encode_opt_box_autoadd_success_action(self.lnurlPayUnprocessedSuccessAction, serializer);
sse_encode_opt_String(self.lnurlWithdrawEndpoint, serializer);
}
@protected
void sse_encode_ln_url_pay_error(LnUrlPayError self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -6487,16 +6352,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_opt_box_autoadd_ln_url_info(LnUrlInfo? 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_ln_url_info(self, serializer);
}
}
@protected
void sse_encode_opt_box_autoadd_pay_amount(PayAmount? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -6578,16 +6433,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_opt_list_payment_state(List<PaymentState>? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_list_payment_state(self, serializer);
}
}
@protected
void sse_encode_opt_list_payment_type(List<PaymentType>? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -6645,7 +6490,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
bolt11: final bolt11,
bolt12Offer: final bolt12Offer,
paymentHash: final paymentHash,
lnurlInfo: final lnurlInfo,
refundTxId: final refundTxId,
refundTxAmountSat: final refundTxAmountSat
):
@@ -6656,7 +6500,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_String(bolt11, serializer);
sse_encode_opt_String(bolt12Offer, serializer);
sse_encode_opt_String(paymentHash, serializer);
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):
@@ -6788,8 +6631,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_send_destination(self.destination, serializer);
sse_encode_u_64(self.feesSat, serializer);
sse_encode_ln_url_pay_request_data(self.data, serializer);
sse_encode_opt_String(self.comment, serializer);
sse_encode_opt_box_autoadd_success_action(self.successAction, serializer);
}
@@ -6974,8 +6815,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
case SdkEvent_PaymentWaitingConfirmation(details: final details):
sse_encode_i_32(5, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case SdkEvent_Synced():
case SdkEvent_PaymentWaitingFeeAcceptance(details: final details):
sse_encode_i_32(6, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case SdkEvent_Synced():
sse_encode_i_32(7, serializer);
default:
throw UnimplementedError('');
}

View File

@@ -2280,6 +2280,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.breez_api_key = cst_encode_opt_String(apiObj.breezApiKey);
wireObj.external_input_parsers = cst_encode_opt_list_external_input_parser(apiObj.externalInputParsers);
wireObj.use_default_external_input_parsers = cst_encode_bool(apiObj.useDefaultExternalInputParsers);
wireObj.onchain_fee_rate_leeway_sat_per_vbyte =
cst_encode_opt_box_autoadd_u_32(apiObj.onchainFeeRateLeewaySatPerVbyte);
}
@protected
@@ -3217,8 +3219,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.kind.PaymentWaitingConfirmation.details = pre_details;
return;
}
if (apiObj is SdkEvent_Synced) {
if (apiObj is SdkEvent_PaymentWaitingFeeAcceptance) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 6;
wireObj.kind.PaymentWaitingFeeAcceptance.details = pre_details;
return;
}
if (apiObj is SdkEvent_Synced) {
wireObj.tag = 7;
return;
}
}
@@ -6222,6 +6230,10 @@ final class wire_cst_SdkEvent_PaymentWaitingConfirmation extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_SdkEvent_PaymentWaitingFeeAcceptance extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class SdkEventKind extends ffi.Union {
external wire_cst_SdkEvent_PaymentFailed PaymentFailed;
@@ -6234,6 +6246,8 @@ final class SdkEventKind extends ffi.Union {
external wire_cst_SdkEvent_PaymentSucceeded PaymentSucceeded;
external wire_cst_SdkEvent_PaymentWaitingConfirmation PaymentWaitingConfirmation;
external wire_cst_SdkEvent_PaymentWaitingFeeAcceptance PaymentWaitingFeeAcceptance;
}
final class wire_cst_sdk_event extends ffi.Struct {
@@ -6288,6 +6302,8 @@ final class wire_cst_config extends ffi.Struct {
@ffi.Bool()
external bool use_default_external_input_parsers;
external ffi.Pointer<ffi.Uint32> onchain_fee_rate_leeway_sat_per_vbyte;
}
final class wire_cst_connect_request extends ffi.Struct {
@@ -6906,6 +6922,8 @@ final class wire_cst_sign_message_response extends ffi.Struct {
const int ESTIMATED_BTC_CLAIM_TX_VSIZE = 111;
const int ESTIMATED_BTC_LOCKUP_TX_VSIZE = 154;
const double STANDARD_FEE_RATE_SAT_PER_VBYTE = 0.1;
const double LOWBALL_FEE_RATE_SAT_PER_VBYTE = 0.01;

View File

@@ -154,6 +154,14 @@ class Config {
/// Set this to false in order to prevent their use.
final bool useDefaultExternalInputParsers;
/// For payments where the onchain fees can only be estimated on creation, this can be used
/// in order to automatically allow slightly more expensive fees. If the actual fee rate ends up
/// being above the sum of the initial estimate and this leeway, the payment will require
/// user fee acceptance. See [WaitingFeeAcceptance](PaymentState::WaitingFeeAcceptance).
///
/// Defaults to zero.
final int? onchainFeeRateLeewaySatPerVbyte;
const Config({
required this.liquidElectrumUrl,
required this.bitcoinElectrumUrl,
@@ -168,6 +176,7 @@ class Config {
this.breezApiKey,
this.externalInputParsers,
required this.useDefaultExternalInputParsers,
this.onchainFeeRateLeewaySatPerVbyte,
});
@override
@@ -184,7 +193,8 @@ class Config {
zeroConfMaxAmountSat.hashCode ^
breezApiKey.hashCode ^
externalInputParsers.hashCode ^
useDefaultExternalInputParsers.hashCode;
useDefaultExternalInputParsers.hashCode ^
onchainFeeRateLeewaySatPerVbyte.hashCode;
@override
bool operator ==(Object other) =>
@@ -203,7 +213,8 @@ class Config {
zeroConfMaxAmountSat == other.zeroConfMaxAmountSat &&
breezApiKey == other.breezApiKey &&
externalInputParsers == other.externalInputParsers &&
useDefaultExternalInputParsers == other.useDefaultExternalInputParsers;
useDefaultExternalInputParsers == other.useDefaultExternalInputParsers &&
onchainFeeRateLeewaySatPerVbyte == other.onchainFeeRateLeewaySatPerVbyte;
}
/// An argument when calling [crate::sdk::LiquidSdk::connect].
@@ -817,6 +828,19 @@ enum PaymentState {
///
/// When the refund tx is broadcast, `refund_tx_id` is set in the swap.
refundPending,
/// ## Chain Swaps
///
/// This is the state when the user needs to accept new fees before the payment can proceed.
///
/// Use [LiquidSdk::fetch_payment_proposed_fees](crate::sdk::LiquidSdk::fetch_payment_proposed_fees)
/// to find out the current fees and
/// [LiquidSdk::accept_payment_proposed_fees](crate::sdk::LiquidSdk::accept_payment_proposed_fees)
/// to accept them, allowing the payment to proceed.
///
/// Otherwise, this payment can be immediately refunded using
/// [prepare_refund](crate::sdk::LiquidSdk::prepare_refund)/[refund](crate::sdk::LiquidSdk::refund).
waitingFeeAcceptance,
;
}
@@ -1385,6 +1409,9 @@ sealed class SdkEvent with _$SdkEvent {
const factory SdkEvent.paymentWaitingConfirmation({
required Payment details,
}) = SdkEvent_PaymentWaitingConfirmation;
const factory SdkEvent.paymentWaitingFeeAcceptance({
required Payment details,
}) = SdkEvent_PaymentWaitingFeeAcceptance;
const factory SdkEvent.synced() = SdkEvent_Synced;
}

View File

@@ -1724,6 +1724,88 @@ abstract class SdkEvent_PaymentWaitingConfirmation extends SdkEvent {
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWith<$Res> {
factory _$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWith(
_$SdkEvent_PaymentWaitingFeeAcceptanceImpl value,
$Res Function(_$SdkEvent_PaymentWaitingFeeAcceptanceImpl) then) =
__$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWithImpl<$Res>
extends _$SdkEventCopyWithImpl<$Res, _$SdkEvent_PaymentWaitingFeeAcceptanceImpl>
implements _$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWith<$Res> {
__$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWithImpl(_$SdkEvent_PaymentWaitingFeeAcceptanceImpl _value,
$Res Function(_$SdkEvent_PaymentWaitingFeeAcceptanceImpl) _then)
: super(_value, _then);
/// Create a copy of SdkEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$SdkEvent_PaymentWaitingFeeAcceptanceImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$SdkEvent_PaymentWaitingFeeAcceptanceImpl extends SdkEvent_PaymentWaitingFeeAcceptance {
const _$SdkEvent_PaymentWaitingFeeAcceptanceImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'SdkEvent.paymentWaitingFeeAcceptance(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SdkEvent_PaymentWaitingFeeAcceptanceImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
/// Create a copy of SdkEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWith<_$SdkEvent_PaymentWaitingFeeAcceptanceImpl>
get copyWith => __$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWithImpl<
_$SdkEvent_PaymentWaitingFeeAcceptanceImpl>(this, _$identity);
}
abstract class SdkEvent_PaymentWaitingFeeAcceptance extends SdkEvent {
const factory SdkEvent_PaymentWaitingFeeAcceptance({required final Payment details}) =
_$SdkEvent_PaymentWaitingFeeAcceptanceImpl;
const SdkEvent_PaymentWaitingFeeAcceptance._() : super._();
Payment get details;
/// Create a copy of SdkEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SdkEvent_PaymentWaitingFeeAcceptanceImplCopyWith<_$SdkEvent_PaymentWaitingFeeAcceptanceImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$SdkEvent_SyncedImplCopyWith<$Res> {
factory _$$SdkEvent_SyncedImplCopyWith(

View File

@@ -4642,6 +4642,10 @@ final class wire_cst_SdkEvent_PaymentWaitingConfirmation extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_SdkEvent_PaymentWaitingFeeAcceptance extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class SdkEventKind extends ffi.Union {
external wire_cst_SdkEvent_PaymentFailed PaymentFailed;
@@ -4654,6 +4658,8 @@ final class SdkEventKind extends ffi.Union {
external wire_cst_SdkEvent_PaymentSucceeded PaymentSucceeded;
external wire_cst_SdkEvent_PaymentWaitingConfirmation PaymentWaitingConfirmation;
external wire_cst_SdkEvent_PaymentWaitingFeeAcceptance PaymentWaitingFeeAcceptance;
}
final class wire_cst_sdk_event extends ffi.Struct {
@@ -4708,6 +4714,8 @@ final class wire_cst_config extends ffi.Struct {
@ffi.Bool()
external bool use_default_external_input_parsers;
external ffi.Pointer<ffi.Uint32> onchain_fee_rate_leeway_sat_per_vbyte;
}
final class wire_cst_connect_request extends ffi.Struct {
@@ -5367,6 +5375,8 @@ typedef DartUniFfiRustFutureContinuationFunction = void Function(ffi.Pointer<ffi
const int ESTIMATED_BTC_CLAIM_TX_VSIZE = 111;
const int ESTIMATED_BTC_LOCKUP_TX_VSIZE = 154;
const double STANDARD_FEE_RATE_SAT_PER_VBYTE = 0.1;
const double LOWBALL_FEE_RATE_SAT_PER_VBYTE = 0.01;

View File

@@ -3,6 +3,36 @@ import breez_sdk_liquid.*
import com.facebook.react.bridge.*
import java.util.*
fun asAcceptPaymentProposedFeesRequest(acceptPaymentProposedFeesRequest: ReadableMap): AcceptPaymentProposedFeesRequest? {
if (!validateMandatoryFields(
acceptPaymentProposedFeesRequest,
arrayOf(
"response",
),
)
) {
return null
}
val response = acceptPaymentProposedFeesRequest.getMap("response")?.let { asFetchPaymentProposedFeesResponse(it) }!!
return AcceptPaymentProposedFeesRequest(response)
}
fun readableMapOf(acceptPaymentProposedFeesRequest: AcceptPaymentProposedFeesRequest): ReadableMap =
readableMapOf(
"response" to readableMapOf(acceptPaymentProposedFeesRequest.response),
)
fun asAcceptPaymentProposedFeesRequestList(arr: ReadableArray): List<AcceptPaymentProposedFeesRequest> {
val list = ArrayList<AcceptPaymentProposedFeesRequest>()
for (value in arr.toList()) {
when (value) {
is ReadableMap -> list.add(asAcceptPaymentProposedFeesRequest(value)!!)
else -> throw SdkException.Generic(errUnexpectedType(value))
}
}
return list
}
fun asAesSuccessActionData(aesSuccessActionData: ReadableMap): AesSuccessActionData? {
if (!validateMandatoryFields(
aesSuccessActionData,
@@ -284,6 +314,16 @@ fun asConfig(config: ReadableMap): Config? {
} else {
null
}
val onchainFeeRateLeewaySatPerVbyte =
if (hasNonNullKey(
config,
"onchainFeeRateLeewaySatPerVbyte",
)
) {
config.getInt("onchainFeeRateLeewaySatPerVbyte").toUInt()
} else {
null
}
return Config(
liquidElectrumUrl,
bitcoinElectrumUrl,
@@ -298,6 +338,7 @@ fun asConfig(config: ReadableMap): Config? {
zeroConfMaxAmountSat,
useDefaultExternalInputParsers,
externalInputParsers,
onchainFeeRateLeewaySatPerVbyte,
)
}
@@ -316,6 +357,7 @@ fun readableMapOf(config: Config): ReadableMap =
"zeroConfMaxAmountSat" to config.zeroConfMaxAmountSat,
"useDefaultExternalInputParsers" to config.useDefaultExternalInputParsers,
"externalInputParsers" to config.externalInputParsers?.let { readableArrayOf(it) },
"onchainFeeRateLeewaySatPerVbyte" to config.onchainFeeRateLeewaySatPerVbyte,
)
fun asConfigList(arr: ReadableArray): List<Config> {
@@ -473,6 +515,72 @@ fun asExternalInputParserList(arr: ReadableArray): List<ExternalInputParser> {
return list
}
fun asFetchPaymentProposedFeesRequest(fetchPaymentProposedFeesRequest: ReadableMap): FetchPaymentProposedFeesRequest? {
if (!validateMandatoryFields(
fetchPaymentProposedFeesRequest,
arrayOf(
"swapId",
),
)
) {
return null
}
val swapId = fetchPaymentProposedFeesRequest.getString("swapId")!!
return FetchPaymentProposedFeesRequest(swapId)
}
fun readableMapOf(fetchPaymentProposedFeesRequest: FetchPaymentProposedFeesRequest): ReadableMap =
readableMapOf(
"swapId" to fetchPaymentProposedFeesRequest.swapId,
)
fun asFetchPaymentProposedFeesRequestList(arr: ReadableArray): List<FetchPaymentProposedFeesRequest> {
val list = ArrayList<FetchPaymentProposedFeesRequest>()
for (value in arr.toList()) {
when (value) {
is ReadableMap -> list.add(asFetchPaymentProposedFeesRequest(value)!!)
else -> throw SdkException.Generic(errUnexpectedType(value))
}
}
return list
}
fun asFetchPaymentProposedFeesResponse(fetchPaymentProposedFeesResponse: ReadableMap): FetchPaymentProposedFeesResponse? {
if (!validateMandatoryFields(
fetchPaymentProposedFeesResponse,
arrayOf(
"swapId",
"feesSat",
"payerAmountSat",
),
)
) {
return null
}
val swapId = fetchPaymentProposedFeesResponse.getString("swapId")!!
val feesSat = fetchPaymentProposedFeesResponse.getDouble("feesSat").toULong()
val payerAmountSat = fetchPaymentProposedFeesResponse.getDouble("payerAmountSat").toULong()
return FetchPaymentProposedFeesResponse(swapId, feesSat, payerAmountSat)
}
fun readableMapOf(fetchPaymentProposedFeesResponse: FetchPaymentProposedFeesResponse): ReadableMap =
readableMapOf(
"swapId" to fetchPaymentProposedFeesResponse.swapId,
"feesSat" to fetchPaymentProposedFeesResponse.feesSat,
"payerAmountSat" to fetchPaymentProposedFeesResponse.payerAmountSat,
)
fun asFetchPaymentProposedFeesResponseList(arr: ReadableArray): List<FetchPaymentProposedFeesResponse> {
val list = ArrayList<FetchPaymentProposedFeesResponse>()
for (value in arr.toList()) {
when (value) {
is ReadableMap -> list.add(asFetchPaymentProposedFeesResponse(value)!!)
else -> throw SdkException.Generic(errUnexpectedType(value))
}
}
return list
}
fun asFiatCurrency(fiatCurrency: ReadableMap): FiatCurrency? {
if (!validateMandatoryFields(
fiatCurrency,
@@ -3230,6 +3338,10 @@ fun asSdkEvent(sdkEvent: ReadableMap): SdkEvent? {
val details = sdkEvent.getMap("details")?.let { asPayment(it) }!!
return SdkEvent.PaymentWaitingConfirmation(details)
}
if (type == "paymentWaitingFeeAcceptance") {
val details = sdkEvent.getMap("details")?.let { asPayment(it) }!!
return SdkEvent.PaymentWaitingFeeAcceptance(details)
}
if (type == "synced") {
return SdkEvent.Synced
}
@@ -3263,6 +3375,10 @@ fun readableMapOf(sdkEvent: SdkEvent): ReadableMap? {
pushToMap(map, "type", "paymentWaitingConfirmation")
pushToMap(map, "details", readableMapOf(sdkEvent.details))
}
is SdkEvent.PaymentWaitingFeeAcceptance -> {
pushToMap(map, "type", "paymentWaitingFeeAcceptance")
pushToMap(map, "details", readableMapOf(sdkEvent.details))
}
is SdkEvent.Synced -> {
pushToMap(map, "type", "synced")
}

View File

@@ -420,6 +420,42 @@ class BreezSDKLiquidModule(
}
}
@ReactMethod
fun fetchPaymentProposedFees(
req: ReadableMap,
promise: Promise,
) {
executor.execute {
try {
val fetchPaymentProposedFeesRequest =
asFetchPaymentProposedFeesRequest(req)
?: run { throw SdkException.Generic(errMissingMandatoryField("req", "FetchPaymentProposedFeesRequest")) }
val res = getBindingLiquidSdk().fetchPaymentProposedFees(fetchPaymentProposedFeesRequest)
promise.resolve(readableMapOf(res))
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
@ReactMethod
fun acceptPaymentProposedFees(
req: ReadableMap,
promise: Promise,
) {
executor.execute {
try {
val acceptPaymentProposedFeesRequest =
asAcceptPaymentProposedFeesRequest(req)
?: run { throw SdkException.Generic(errMissingMandatoryField("req", "AcceptPaymentProposedFeesRequest")) }
getBindingLiquidSdk().acceptPaymentProposedFees(acceptPaymentProposedFeesRequest)
promise.resolve(readableMapOf("status" to "ok"))
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
@ReactMethod
fun listRefundables(promise: Promise) {
executor.execute {

View File

@@ -2,6 +2,38 @@ import BreezSDKLiquid
import Foundation
enum BreezSDKLiquidMapper {
static func asAcceptPaymentProposedFeesRequest(acceptPaymentProposedFeesRequest: [String: Any?]) throws -> AcceptPaymentProposedFeesRequest {
guard let responseTmp = acceptPaymentProposedFeesRequest["response"] as? [String: Any?] else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "response", typeName: "AcceptPaymentProposedFeesRequest"))
}
let response = try asFetchPaymentProposedFeesResponse(fetchPaymentProposedFeesResponse: responseTmp)
return AcceptPaymentProposedFeesRequest(response: response)
}
static func dictionaryOf(acceptPaymentProposedFeesRequest: AcceptPaymentProposedFeesRequest) -> [String: Any?] {
return [
"response": dictionaryOf(fetchPaymentProposedFeesResponse: acceptPaymentProposedFeesRequest.response),
]
}
static func asAcceptPaymentProposedFeesRequestList(arr: [Any]) throws -> [AcceptPaymentProposedFeesRequest] {
var list = [AcceptPaymentProposedFeesRequest]()
for value in arr {
if let val = value as? [String: Any?] {
var acceptPaymentProposedFeesRequest = try asAcceptPaymentProposedFeesRequest(acceptPaymentProposedFeesRequest: val)
list.append(acceptPaymentProposedFeesRequest)
} else {
throw SdkError.Generic(message: errUnexpectedType(typeName: "AcceptPaymentProposedFeesRequest"))
}
}
return list
}
static func arrayOf(acceptPaymentProposedFeesRequestList: [AcceptPaymentProposedFeesRequest]) -> [Any] {
return acceptPaymentProposedFeesRequestList.map { v -> [String: Any?] in return dictionaryOf(acceptPaymentProposedFeesRequest: v) }
}
static func asAesSuccessActionData(aesSuccessActionData: [String: Any?]) throws -> AesSuccessActionData {
guard let description = aesSuccessActionData["description"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "description", typeName: "AesSuccessActionData"))
@@ -307,9 +339,6 @@ enum BreezSDKLiquidMapper {
guard let zeroConfMinFeeRateMsat = config["zeroConfMinFeeRateMsat"] as? UInt32 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "zeroConfMinFeeRateMsat", typeName: "Config"))
}
guard let syncServiceUrl = config["syncServiceUrl"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "syncServiceUrl", typeName: "Config"))
}
var breezApiKey: String?
if hasNonNilKey(data: config, key: "breezApiKey") {
guard let breezApiKeyTmp = config["breezApiKey"] as? String else {
@@ -339,7 +368,15 @@ enum BreezSDKLiquidMapper {
externalInputParsers = try asExternalInputParserList(arr: externalInputParsersTmp)
}
return Config(liquidElectrumUrl: liquidElectrumUrl, bitcoinElectrumUrl: bitcoinElectrumUrl, mempoolspaceUrl: mempoolspaceUrl, workingDir: workingDir, network: network, paymentTimeoutSec: paymentTimeoutSec, zeroConfMinFeeRateMsat: zeroConfMinFeeRateMsat, syncServiceUrl: syncServiceUrl, breezApiKey: breezApiKey, cacheDir: cacheDir, zeroConfMaxAmountSat: zeroConfMaxAmountSat, useDefaultExternalInputParsers: useDefaultExternalInputParsers, externalInputParsers: externalInputParsers)
var onchainFeeRateLeewaySatPerVbyte: UInt32?
if hasNonNilKey(data: config, key: "onchainFeeRateLeewaySatPerVbyte") {
guard let onchainFeeRateLeewaySatPerVbyteTmp = config["onchainFeeRateLeewaySatPerVbyte"] as? UInt32 else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "onchainFeeRateLeewaySatPerVbyte"))
}
onchainFeeRateLeewaySatPerVbyte = onchainFeeRateLeewaySatPerVbyteTmp
}
return Config(liquidElectrumUrl: liquidElectrumUrl, bitcoinElectrumUrl: bitcoinElectrumUrl, mempoolspaceUrl: mempoolspaceUrl, workingDir: workingDir, network: network, paymentTimeoutSec: paymentTimeoutSec, zeroConfMinFeeRateMsat: zeroConfMinFeeRateMsat, breezApiKey: breezApiKey, cacheDir: cacheDir, zeroConfMaxAmountSat: zeroConfMaxAmountSat, useDefaultExternalInputParsers: useDefaultExternalInputParsers, externalInputParsers: externalInputParsers, onchainFeeRateLeewaySatPerVbyte: onchainFeeRateLeewaySatPerVbyte)
}
static func dictionaryOf(config: Config) -> [String: Any?] {
@@ -351,12 +388,12 @@ enum BreezSDKLiquidMapper {
"network": valueOf(liquidNetwork: config.network),
"paymentTimeoutSec": config.paymentTimeoutSec,
"zeroConfMinFeeRateMsat": config.zeroConfMinFeeRateMsat,
"syncServiceUrl": config.syncServiceUrl,
"breezApiKey": config.breezApiKey == nil ? nil : config.breezApiKey,
"cacheDir": config.cacheDir == nil ? nil : config.cacheDir,
"zeroConfMaxAmountSat": config.zeroConfMaxAmountSat == nil ? nil : config.zeroConfMaxAmountSat,
"useDefaultExternalInputParsers": config.useDefaultExternalInputParsers,
"externalInputParsers": config.externalInputParsers == nil ? nil : arrayOf(externalInputParserList: config.externalInputParsers!),
"onchainFeeRateLeewaySatPerVbyte": config.onchainFeeRateLeewaySatPerVbyte == nil ? nil : config.onchainFeeRateLeewaySatPerVbyte,
]
}
@@ -551,6 +588,76 @@ enum BreezSDKLiquidMapper {
return externalInputParserList.map { v -> [String: Any?] in return dictionaryOf(externalInputParser: v) }
}
static func asFetchPaymentProposedFeesRequest(fetchPaymentProposedFeesRequest: [String: Any?]) throws -> FetchPaymentProposedFeesRequest {
guard let swapId = fetchPaymentProposedFeesRequest["swapId"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "swapId", typeName: "FetchPaymentProposedFeesRequest"))
}
return FetchPaymentProposedFeesRequest(swapId: swapId)
}
static func dictionaryOf(fetchPaymentProposedFeesRequest: FetchPaymentProposedFeesRequest) -> [String: Any?] {
return [
"swapId": fetchPaymentProposedFeesRequest.swapId,
]
}
static func asFetchPaymentProposedFeesRequestList(arr: [Any]) throws -> [FetchPaymentProposedFeesRequest] {
var list = [FetchPaymentProposedFeesRequest]()
for value in arr {
if let val = value as? [String: Any?] {
var fetchPaymentProposedFeesRequest = try asFetchPaymentProposedFeesRequest(fetchPaymentProposedFeesRequest: val)
list.append(fetchPaymentProposedFeesRequest)
} else {
throw SdkError.Generic(message: errUnexpectedType(typeName: "FetchPaymentProposedFeesRequest"))
}
}
return list
}
static func arrayOf(fetchPaymentProposedFeesRequestList: [FetchPaymentProposedFeesRequest]) -> [Any] {
return fetchPaymentProposedFeesRequestList.map { v -> [String: Any?] in return dictionaryOf(fetchPaymentProposedFeesRequest: v) }
}
static func asFetchPaymentProposedFeesResponse(fetchPaymentProposedFeesResponse: [String: Any?]) throws -> FetchPaymentProposedFeesResponse {
guard let swapId = fetchPaymentProposedFeesResponse["swapId"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "swapId", typeName: "FetchPaymentProposedFeesResponse"))
}
guard let feesSat = fetchPaymentProposedFeesResponse["feesSat"] as? UInt64 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "FetchPaymentProposedFeesResponse"))
}
guard let payerAmountSat = fetchPaymentProposedFeesResponse["payerAmountSat"] as? UInt64 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "payerAmountSat", typeName: "FetchPaymentProposedFeesResponse"))
}
return FetchPaymentProposedFeesResponse(swapId: swapId, feesSat: feesSat, payerAmountSat: payerAmountSat)
}
static func dictionaryOf(fetchPaymentProposedFeesResponse: FetchPaymentProposedFeesResponse) -> [String: Any?] {
return [
"swapId": fetchPaymentProposedFeesResponse.swapId,
"feesSat": fetchPaymentProposedFeesResponse.feesSat,
"payerAmountSat": fetchPaymentProposedFeesResponse.payerAmountSat,
]
}
static func asFetchPaymentProposedFeesResponseList(arr: [Any]) throws -> [FetchPaymentProposedFeesResponse] {
var list = [FetchPaymentProposedFeesResponse]()
for value in arr {
if let val = value as? [String: Any?] {
var fetchPaymentProposedFeesResponse = try asFetchPaymentProposedFeesResponse(fetchPaymentProposedFeesResponse: val)
list.append(fetchPaymentProposedFeesResponse)
} else {
throw SdkError.Generic(message: errUnexpectedType(typeName: "FetchPaymentProposedFeesResponse"))
}
}
return list
}
static func arrayOf(fetchPaymentProposedFeesResponseList: [FetchPaymentProposedFeesResponse]) -> [Any] {
return fetchPaymentProposedFeesResponseList.map { v -> [String: Any?] in return dictionaryOf(fetchPaymentProposedFeesResponse: v) }
}
static func asFiatCurrency(fiatCurrency: [String: Any?]) throws -> FiatCurrency {
guard let id = fiatCurrency["id"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "id", typeName: "FiatCurrency"))
@@ -956,11 +1063,6 @@ enum BreezSDKLiquidMapper {
filters = try asPaymentTypeList(arr: filtersTmp)
}
var states: [PaymentState]?
if let statesTmp = listPaymentsRequest["states"] as? [String] {
states = try asPaymentStateList(arr: statesTmp)
}
var fromTimestamp: Int64?
if hasNonNilKey(data: listPaymentsRequest, key: "fromTimestamp") {
guard let fromTimestampTmp = listPaymentsRequest["fromTimestamp"] as? Int64 else {
@@ -994,13 +1096,12 @@ enum BreezSDKLiquidMapper {
details = try asListPaymentDetails(listPaymentDetails: detailsTmp)
}
return ListPaymentsRequest(filters: filters, states: states, fromTimestamp: fromTimestamp, toTimestamp: toTimestamp, offset: offset, limit: limit, details: details)
return ListPaymentsRequest(filters: filters, fromTimestamp: fromTimestamp, toTimestamp: toTimestamp, offset: offset, limit: limit, details: details)
}
static func dictionaryOf(listPaymentsRequest: ListPaymentsRequest) -> [String: Any?] {
return [
"filters": listPaymentsRequest.filters == nil ? nil : arrayOf(paymentTypeList: listPaymentsRequest.filters!),
"states": listPaymentsRequest.states == nil ? nil : arrayOf(paymentStateList: listPaymentsRequest.states!),
"fromTimestamp": listPaymentsRequest.fromTimestamp == nil ? nil : listPaymentsRequest.fromTimestamp,
"toTimestamp": listPaymentsRequest.toTimestamp == nil ? nil : listPaymentsRequest.toTimestamp,
"offset": listPaymentsRequest.offset == nil ? nil : listPaymentsRequest.offset,
@@ -1135,85 +1236,6 @@ enum BreezSDKLiquidMapper {
return lnUrlErrorDataList.map { v -> [String: Any?] in return dictionaryOf(lnUrlErrorData: v) }
}
static func asLnUrlInfo(lnUrlInfo: [String: Any?]) throws -> LnUrlInfo {
var lnAddress: String?
if hasNonNilKey(data: lnUrlInfo, key: "lnAddress") {
guard let lnAddressTmp = lnUrlInfo["lnAddress"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnAddress"))
}
lnAddress = lnAddressTmp
}
var lnurlPayComment: String?
if hasNonNilKey(data: lnUrlInfo, key: "lnurlPayComment") {
guard let lnurlPayCommentTmp = lnUrlInfo["lnurlPayComment"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlPayComment"))
}
lnurlPayComment = lnurlPayCommentTmp
}
var lnurlPayDomain: String?
if hasNonNilKey(data: lnUrlInfo, key: "lnurlPayDomain") {
guard let lnurlPayDomainTmp = lnUrlInfo["lnurlPayDomain"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlPayDomain"))
}
lnurlPayDomain = lnurlPayDomainTmp
}
var lnurlPayMetadata: String?
if hasNonNilKey(data: lnUrlInfo, key: "lnurlPayMetadata") {
guard let lnurlPayMetadataTmp = lnUrlInfo["lnurlPayMetadata"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlPayMetadata"))
}
lnurlPayMetadata = lnurlPayMetadataTmp
}
var lnurlPaySuccessAction: SuccessActionProcessed?
if let lnurlPaySuccessActionTmp = lnUrlInfo["lnurlPaySuccessAction"] as? [String: Any?] {
lnurlPaySuccessAction = try asSuccessActionProcessed(successActionProcessed: lnurlPaySuccessActionTmp)
}
var lnurlPayUnprocessedSuccessAction: SuccessAction?
if let lnurlPayUnprocessedSuccessActionTmp = lnUrlInfo["lnurlPayUnprocessedSuccessAction"] as? [String: Any?] {
lnurlPayUnprocessedSuccessAction = try asSuccessAction(successAction: lnurlPayUnprocessedSuccessActionTmp)
}
var lnurlWithdrawEndpoint: String?
if hasNonNilKey(data: lnUrlInfo, key: "lnurlWithdrawEndpoint") {
guard let lnurlWithdrawEndpointTmp = lnUrlInfo["lnurlWithdrawEndpoint"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlWithdrawEndpoint"))
}
lnurlWithdrawEndpoint = lnurlWithdrawEndpointTmp
}
return LnUrlInfo(lnAddress: lnAddress, lnurlPayComment: lnurlPayComment, lnurlPayDomain: lnurlPayDomain, lnurlPayMetadata: lnurlPayMetadata, lnurlPaySuccessAction: lnurlPaySuccessAction, lnurlPayUnprocessedSuccessAction: lnurlPayUnprocessedSuccessAction, lnurlWithdrawEndpoint: lnurlWithdrawEndpoint)
}
static func dictionaryOf(lnUrlInfo: LnUrlInfo) -> [String: Any?] {
return [
"lnAddress": lnUrlInfo.lnAddress == nil ? nil : lnUrlInfo.lnAddress,
"lnurlPayComment": lnUrlInfo.lnurlPayComment == nil ? nil : lnUrlInfo.lnurlPayComment,
"lnurlPayDomain": lnUrlInfo.lnurlPayDomain == nil ? nil : lnUrlInfo.lnurlPayDomain,
"lnurlPayMetadata": lnUrlInfo.lnurlPayMetadata == nil ? nil : lnUrlInfo.lnurlPayMetadata,
"lnurlPaySuccessAction": lnUrlInfo.lnurlPaySuccessAction == nil ? nil : dictionaryOf(successActionProcessed: lnUrlInfo.lnurlPaySuccessAction!),
"lnurlPayUnprocessedSuccessAction": lnUrlInfo.lnurlPayUnprocessedSuccessAction == nil ? nil : dictionaryOf(successAction: lnUrlInfo.lnurlPayUnprocessedSuccessAction!),
"lnurlWithdrawEndpoint": lnUrlInfo.lnurlWithdrawEndpoint == nil ? nil : lnUrlInfo.lnurlWithdrawEndpoint,
]
}
static func asLnUrlInfoList(arr: [Any]) throws -> [LnUrlInfo] {
var list = [LnUrlInfo]()
for value in arr {
if let val = value as? [String: Any?] {
var lnUrlInfo = try asLnUrlInfo(lnUrlInfo: val)
list.append(lnUrlInfo)
} else {
throw SdkError.Generic(message: errUnexpectedType(typeName: "LnUrlInfo"))
}
}
return list
}
static func arrayOf(lnUrlInfoList: [LnUrlInfo]) -> [Any] {
return lnUrlInfoList.map { v -> [String: Any?] in return dictionaryOf(lnUrlInfo: v) }
}
static func asLnUrlPayErrorData(lnUrlPayErrorData: [String: Any?]) throws -> LnUrlPayErrorData {
guard let paymentHash = lnUrlPayErrorData["paymentHash"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentHash", typeName: "LnUrlPayErrorData"))
@@ -1962,32 +1984,18 @@ enum BreezSDKLiquidMapper {
guard let feesSat = prepareLnUrlPayResponse["feesSat"] as? UInt64 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PrepareLnUrlPayResponse"))
}
guard let dataTmp = prepareLnUrlPayResponse["data"] as? [String: Any?] else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "data", typeName: "PrepareLnUrlPayResponse"))
}
let data = try asLnUrlPayRequestData(lnUrlPayRequestData: dataTmp)
var comment: String?
if hasNonNilKey(data: prepareLnUrlPayResponse, key: "comment") {
guard let commentTmp = prepareLnUrlPayResponse["comment"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "comment"))
}
comment = commentTmp
}
var successAction: SuccessAction?
if let successActionTmp = prepareLnUrlPayResponse["successAction"] as? [String: Any?] {
successAction = try asSuccessAction(successAction: successActionTmp)
}
return PrepareLnUrlPayResponse(destination: destination, feesSat: feesSat, data: data, comment: comment, successAction: successAction)
return PrepareLnUrlPayResponse(destination: destination, feesSat: feesSat, successAction: successAction)
}
static func dictionaryOf(prepareLnUrlPayResponse: PrepareLnUrlPayResponse) -> [String: Any?] {
return [
"destination": dictionaryOf(sendDestination: prepareLnUrlPayResponse.destination),
"feesSat": prepareLnUrlPayResponse.feesSat,
"data": dictionaryOf(lnUrlPayRequestData: prepareLnUrlPayResponse.data),
"comment": prepareLnUrlPayResponse.comment == nil ? nil : prepareLnUrlPayResponse.comment,
"successAction": prepareLnUrlPayResponse.successAction == nil ? nil : dictionaryOf(successAction: prepareLnUrlPayResponse.successAction!),
]
}
@@ -3764,16 +3772,11 @@ enum BreezSDKLiquidMapper {
let _paymentHash = paymentDetails["paymentHash"] as? String
var _lnurlInfo: LnUrlInfo?
if let lnurlInfoTmp = paymentDetails["lnurlInfo"] as? [String: Any?] {
_lnurlInfo = try asLnUrlInfo(lnUrlInfo: lnurlInfoTmp)
}
let _refundTxId = paymentDetails["refundTxId"] as? String
let _refundTxAmountSat = paymentDetails["refundTxAmountSat"] as? UInt64
return PaymentDetails.lightning(swapId: _swapId, description: _description, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, lnurlInfo: _lnurlInfo, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat)
return PaymentDetails.lightning(swapId: _swapId, description: _description, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat)
}
if type == "liquid" {
guard let _destination = paymentDetails["destination"] as? String else {
@@ -3804,7 +3807,7 @@ enum BreezSDKLiquidMapper {
static func dictionaryOf(paymentDetails: PaymentDetails) -> [String: Any?] {
switch paymentDetails {
case let .lightning(
swapId, description, preimage, bolt11, bolt12Offer, paymentHash, lnurlInfo, refundTxId, refundTxAmountSat
swapId, description, preimage, bolt11, bolt12Offer, paymentHash, refundTxId, refundTxAmountSat
):
return [
"type": "lightning",
@@ -3814,7 +3817,6 @@ enum BreezSDKLiquidMapper {
"bolt11": bolt11 == nil ? nil : bolt11,
"bolt12Offer": bolt12Offer == nil ? nil : bolt12Offer,
"paymentHash": paymentHash == nil ? nil : paymentHash,
"lnurlInfo": lnurlInfo == nil ? nil : dictionaryOf(lnUrlInfo: lnurlInfo!),
"refundTxId": refundTxId == nil ? nil : refundTxId,
"refundTxAmountSat": refundTxAmountSat == nil ? nil : refundTxAmountSat,
]
@@ -3926,6 +3928,9 @@ enum BreezSDKLiquidMapper {
case "refundPending":
return PaymentState.refundPending
case "waitingFeeAcceptance":
return PaymentState.waitingFeeAcceptance
default: throw SdkError.Generic(message: "Invalid variant \(paymentState) for enum PaymentState")
}
}
@@ -3952,6 +3957,9 @@ enum BreezSDKLiquidMapper {
case .refundPending:
return "refundPending"
case .waitingFeeAcceptance:
return "waitingFeeAcceptance"
}
}
@@ -4061,6 +4069,14 @@ enum BreezSDKLiquidMapper {
return SdkEvent.paymentWaitingConfirmation(details: _details)
}
if type == "paymentWaitingFeeAcceptance" {
guard let detailsTmp = sdkEvent["details"] as? [String: Any?] else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "SdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return SdkEvent.paymentWaitingFeeAcceptance(details: _details)
}
if type == "synced" {
return SdkEvent.synced
}
@@ -4118,6 +4134,14 @@ enum BreezSDKLiquidMapper {
"details": dictionaryOf(payment: details),
]
case let .paymentWaitingFeeAcceptance(
details
):
return [
"type": "paymentWaitingFeeAcceptance",
"details": dictionaryOf(payment: details),
]
case .synced:
return [
"type": "synced",

View File

@@ -131,6 +131,18 @@ RCT_EXTERN_METHOD(
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
fetchPaymentProposedFees: (NSDictionary*)req
resolve: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
acceptPaymentProposedFees: (NSDictionary*)req
resolve: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
listRefundables: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject

View File

@@ -321,6 +321,28 @@ class RNBreezSDKLiquid: RCTEventEmitter {
}
}
@objc(fetchPaymentProposedFees:resolve:reject:)
func fetchPaymentProposedFees(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
let fetchPaymentProposedFeesRequest = try BreezSDKLiquidMapper.asFetchPaymentProposedFeesRequest(fetchPaymentProposedFeesRequest: req)
var res = try getBindingLiquidSdk().fetchPaymentProposedFees(req: fetchPaymentProposedFeesRequest)
resolve(BreezSDKLiquidMapper.dictionaryOf(fetchPaymentProposedFeesResponse: res))
} catch let err {
rejectErr(err: err, reject: reject)
}
}
@objc(acceptPaymentProposedFees:resolve:reject:)
func acceptPaymentProposedFees(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
let acceptPaymentProposedFeesRequest = try BreezSDKLiquidMapper.asAcceptPaymentProposedFeesRequest(acceptPaymentProposedFeesRequest: req)
try getBindingLiquidSdk().acceptPaymentProposedFees(req: acceptPaymentProposedFeesRequest)
resolve(["status": "ok"])
} catch let err {
rejectErr(err: err, reject: reject)
}
}
@objc(listRefundables:reject:)
func listRefundables(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {

View File

@@ -19,6 +19,10 @@ const BreezSDKLiquid = NativeModules.RNBreezSDKLiquid
const BreezSDKLiquidEmitter = new NativeEventEmitter(BreezSDKLiquid)
export interface AcceptPaymentProposedFeesRequest {
response: FetchPaymentProposedFeesResponse
}
export interface AesSuccessActionData {
description: string
ciphertext: string
@@ -71,6 +75,7 @@ export interface Config {
zeroConfMaxAmountSat?: number
useDefaultExternalInputParsers: boolean
externalInputParsers?: ExternalInputParser[]
onchainFeeRateLeewaySatPerVbyte?: number
}
export interface ConnectRequest {
@@ -98,6 +103,16 @@ export interface ExternalInputParser {
parserUrl: string
}
export interface FetchPaymentProposedFeesRequest {
swapId: string
}
export interface FetchPaymentProposedFeesResponse {
swapId: string
feesSat: number
payerAmountSat: number
}
export interface FiatCurrency {
id: string
info: CurrencyInfo
@@ -648,7 +663,8 @@ export enum PaymentState {
FAILED = "failed",
TIMED_OUT = "timedOut",
REFUNDABLE = "refundable",
REFUND_PENDING = "refundPending"
REFUND_PENDING = "refundPending",
WAITING_FEE_ACCEPTANCE = "waitingFeeAcceptance"
}
export enum PaymentType {
@@ -663,6 +679,7 @@ export enum SdkEventVariant {
PAYMENT_REFUND_PENDING = "paymentRefundPending",
PAYMENT_SUCCEEDED = "paymentSucceeded",
PAYMENT_WAITING_CONFIRMATION = "paymentWaitingConfirmation",
PAYMENT_WAITING_FEE_ACCEPTANCE = "paymentWaitingFeeAcceptance",
SYNCED = "synced"
}
@@ -684,6 +701,9 @@ export type SdkEvent = {
} | {
type: SdkEventVariant.PAYMENT_WAITING_CONFIRMATION,
details: Payment
} | {
type: SdkEventVariant.PAYMENT_WAITING_FEE_ACCEPTANCE,
details: Payment
} | {
type: SdkEventVariant.SYNCED
}
@@ -861,6 +881,15 @@ export const getPayment = async (req: GetPaymentRequest): Promise<Payment | null
return response
}
export const fetchPaymentProposedFees = async (req: FetchPaymentProposedFeesRequest): Promise<FetchPaymentProposedFeesResponse> => {
const response = await BreezSDKLiquid.fetchPaymentProposedFees(req)
return response
}
export const acceptPaymentProposedFees = async (req: AcceptPaymentProposedFeesRequest): Promise<void> => {
await BreezSDKLiquid.acceptPaymentProposedFees(req)
}
export const listRefundables = async (): Promise<RefundableSwap[]> => {
const response = await BreezSDKLiquid.listRefundables()
return response