mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-23 17:04:25 +01:00
prepare-pay-onchain: add option for drain (#464)
* prepare-pay-onchain: add option for drain in req * Fix clippy * ChainSwapStateHandler: gracefully handle building both drain and non-drain lockups * Send Chain swap: use standard feerate when estimating lockup tx fee * UDL: move new drain field above the last PreparePayOnchainRequest optional field * UDL: move new drain field optional * prepare-pay-onchain: treat normal payment as drain if receiver amount is high enough If the receiver amount is as high as it would be in case of drain, treat the current prepare-pay-onchain as drain, even if the drain flag is not set. * build_drain_tx: add optional amount validation * Add PayOnchainAmount enum to cover amount types (drain, receiver) * Add ability to find max_receiver_amount_sat for non-drain sends * Revert "Add ability to find max_receiver_amount_sat for non-drain sends" This reverts commit 60ee1c768021810f72bc64a8ada69d35b638185e. * prepare_pay_onchain: treat drain and non-drain cases separately If the non-drain case is chosen with a receiver_amount equivalent to what drain would have calculated, it results in an error. For drain, the caller has to explicitly choose PayOnchainAmount::Drain. * CLI: send-onchain-payment accepts optional amount * CLI: add docs for send-onchain-payment drain arg * SDK: expand docs for prepare_pay_onchain * Re-generate RN bindings * Re-generate flutter bindings
This commit is contained in:
@@ -47,10 +47,14 @@ pub(crate) enum Command {
|
||||
/// Btc onchain address to send to
|
||||
address: String,
|
||||
|
||||
/// Amount that will be received, in satoshi
|
||||
receiver_amount_sat: u64,
|
||||
/// Amount that will be received, in satoshi. Must be set if `drain` is false or unset.
|
||||
receiver_amount_sat: Option<u64>,
|
||||
|
||||
// The optional fee rate to use, in satoshi/vbyte
|
||||
/// Whether or not this is a drain operation. If true, all available funds will be used.
|
||||
#[arg(short, long)]
|
||||
drain: Option<bool>,
|
||||
|
||||
/// The optional fee rate to use, in satoshi/vbyte
|
||||
#[clap(short = 'f', long = "fee_rate")]
|
||||
sat_per_vbyte: Option<u32>,
|
||||
},
|
||||
@@ -341,19 +345,28 @@ pub(crate) async fn handle_command(
|
||||
Command::SendOnchainPayment {
|
||||
address,
|
||||
receiver_amount_sat,
|
||||
drain,
|
||||
sat_per_vbyte,
|
||||
} => {
|
||||
let amount = match drain.unwrap_or(false) {
|
||||
true => PayOnchainAmount::Drain,
|
||||
false => PayOnchainAmount::Receiver {
|
||||
amount_sat: receiver_amount_sat.ok_or(anyhow::anyhow!(
|
||||
"Must specify `receiver_amount_sat` if not draining"
|
||||
))?,
|
||||
},
|
||||
};
|
||||
let prepare_response = sdk
|
||||
.prepare_pay_onchain(&PreparePayOnchainRequest {
|
||||
receiver_amount_sat,
|
||||
amount,
|
||||
sat_per_vbyte,
|
||||
})
|
||||
.await?;
|
||||
|
||||
wait_confirmation!(
|
||||
format!(
|
||||
"Fees: {} sat (incl claim fee: {} sat). Are the fees acceptable? (y/N) ",
|
||||
prepare_response.total_fees_sat, prepare_response.claim_fees_sat
|
||||
"Fees: {} sat (incl claim fee: {} sat). Receiver amount: {} sat. Are the fees acceptable? (y/N) ",
|
||||
prepare_response.total_fees_sat, prepare_response.claim_fees_sat, prepare_response.receiver_amount_sat
|
||||
),
|
||||
"Payment send halted"
|
||||
);
|
||||
|
||||
@@ -133,8 +133,21 @@ typedef struct wire_cst_prepare_buy_bitcoin_request {
|
||||
uint64_t amount_sat;
|
||||
} wire_cst_prepare_buy_bitcoin_request;
|
||||
|
||||
typedef struct wire_cst_PayOnchainAmount_Receiver {
|
||||
uint64_t amount_sat;
|
||||
} wire_cst_PayOnchainAmount_Receiver;
|
||||
|
||||
typedef union PayOnchainAmountKind {
|
||||
struct wire_cst_PayOnchainAmount_Receiver Receiver;
|
||||
} PayOnchainAmountKind;
|
||||
|
||||
typedef struct wire_cst_pay_onchain_amount {
|
||||
int32_t tag;
|
||||
union PayOnchainAmountKind kind;
|
||||
} wire_cst_pay_onchain_amount;
|
||||
|
||||
typedef struct wire_cst_prepare_pay_onchain_request {
|
||||
uint64_t receiver_amount_sat;
|
||||
struct wire_cst_pay_onchain_amount amount;
|
||||
uint32_t *sat_per_vbyte;
|
||||
} wire_cst_prepare_pay_onchain_request;
|
||||
|
||||
|
||||
@@ -410,8 +410,14 @@ dictionary OnchainPaymentLimitsResponse {
|
||||
Limits receive;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface PayOnchainAmount {
|
||||
Receiver(u64 amount_sat);
|
||||
Drain();
|
||||
};
|
||||
|
||||
dictionary PreparePayOnchainRequest {
|
||||
u64 receiver_amount_sat;
|
||||
PayOnchainAmount amount;
|
||||
u32? sat_per_vbyte = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -582,14 +582,28 @@ impl ChainSwapStateHandler {
|
||||
lockup_details.amount, lockup_details.lockup_address
|
||||
);
|
||||
|
||||
let lockup_tx = self
|
||||
let lockup_tx = match self
|
||||
.onchain_wallet
|
||||
.build_tx(
|
||||
None,
|
||||
&lockup_details.lockup_address,
|
||||
lockup_details.amount as u64,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Err(PaymentError::InsufficientFunds) => {
|
||||
warn!("Cannot build normal lockup tx due to insufficient funds, attempting to build drain tx");
|
||||
self.onchain_wallet
|
||||
.build_drain_tx(
|
||||
None,
|
||||
&lockup_details.lockup_address,
|
||||
Some(lockup_details.amount as u64),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Ok(lockup_tx) => Ok(lockup_tx),
|
||||
}?;
|
||||
|
||||
let lockup_tx_id = self
|
||||
.liquid_chain_service
|
||||
|
||||
@@ -3062,6 +3062,27 @@ impl SseDecode for Option<Vec<crate::model::PaymentType>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::PayOnchainAmount {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut tag_ = <i32>::sse_decode(deserializer);
|
||||
match tag_ {
|
||||
0 => {
|
||||
let mut var_amountSat = <u64>::sse_decode(deserializer);
|
||||
return crate::model::PayOnchainAmount::Receiver {
|
||||
amount_sat: var_amountSat,
|
||||
};
|
||||
}
|
||||
1 => {
|
||||
return crate::model::PayOnchainAmount::Drain;
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::PayOnchainRequest {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -3307,10 +3328,10 @@ impl SseDecode for crate::model::PrepareBuyBitcoinResponse {
|
||||
impl SseDecode for crate::model::PreparePayOnchainRequest {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_receiverAmountSat = <u64>::sse_decode(deserializer);
|
||||
let mut var_amount = <crate::model::PayOnchainAmount>::sse_decode(deserializer);
|
||||
let mut var_satPerVbyte = <Option<u32>>::sse_decode(deserializer);
|
||||
return crate::model::PreparePayOnchainRequest {
|
||||
receiver_amount_sat: var_receiverAmountSat,
|
||||
amount: var_amount,
|
||||
sat_per_vbyte: var_satPerVbyte,
|
||||
};
|
||||
}
|
||||
@@ -4828,6 +4849,31 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::OnchainPaymentLimitsRespons
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::PayOnchainAmount {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
match self {
|
||||
crate::model::PayOnchainAmount::Receiver { amount_sat } => {
|
||||
[0.into_dart(), amount_sat.into_into_dart().into_dart()].into_dart()
|
||||
}
|
||||
crate::model::PayOnchainAmount::Drain => [1.into_dart()].into_dart(),
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
||||
for crate::model::PayOnchainAmount
|
||||
{
|
||||
}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::model::PayOnchainAmount>
|
||||
for crate::model::PayOnchainAmount
|
||||
{
|
||||
fn into_into_dart(self) -> crate::model::PayOnchainAmount {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::PayOnchainRequest {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
@@ -5090,7 +5136,7 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::PrepareBuyBitcoinResponse>
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::PreparePayOnchainRequest {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.receiver_amount_sat.into_into_dart().into_dart(),
|
||||
self.amount.into_into_dart().into_dart(),
|
||||
self.sat_per_vbyte.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
@@ -6532,6 +6578,24 @@ impl SseEncode for Option<Vec<crate::model::PaymentType>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::PayOnchainAmount {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
match self {
|
||||
crate::model::PayOnchainAmount::Receiver { amount_sat } => {
|
||||
<i32>::sse_encode(0, serializer);
|
||||
<u64>::sse_encode(amount_sat, serializer);
|
||||
}
|
||||
crate::model::PayOnchainAmount::Drain => {
|
||||
<i32>::sse_encode(1, serializer);
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::PayOnchainRequest {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
@@ -6760,7 +6824,7 @@ impl SseEncode for crate::model::PrepareBuyBitcoinResponse {
|
||||
impl SseEncode for crate::model::PreparePayOnchainRequest {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<u64>::sse_encode(self.receiver_amount_sat, serializer);
|
||||
<crate::model::PayOnchainAmount>::sse_encode(self.amount, serializer);
|
||||
<Option<u32>>::sse_encode(self.sat_per_vbyte, serializer);
|
||||
}
|
||||
}
|
||||
@@ -8227,6 +8291,21 @@ mod io {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::PayOnchainAmount> for wire_cst_pay_onchain_amount {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::PayOnchainAmount {
|
||||
match self.tag {
|
||||
0 => {
|
||||
let ans = unsafe { self.kind.Receiver };
|
||||
crate::model::PayOnchainAmount::Receiver {
|
||||
amount_sat: ans.amount_sat.cst_decode(),
|
||||
}
|
||||
}
|
||||
1 => crate::model::PayOnchainAmount::Drain,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::PayOnchainRequest> for wire_cst_pay_onchain_request {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::PayOnchainRequest {
|
||||
@@ -8389,7 +8468,7 @@ mod io {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::PreparePayOnchainRequest {
|
||||
crate::model::PreparePayOnchainRequest {
|
||||
receiver_amount_sat: self.receiver_amount_sat.cst_decode(),
|
||||
amount: self.amount.cst_decode(),
|
||||
sat_per_vbyte: self.sat_per_vbyte.cst_decode(),
|
||||
}
|
||||
}
|
||||
@@ -9277,6 +9356,19 @@ mod io {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_pay_onchain_amount {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
tag: -1,
|
||||
kind: PayOnchainAmountKind { nil__: () },
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for wire_cst_pay_onchain_amount {
|
||||
fn default() -> Self {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_pay_onchain_request {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
@@ -9365,7 +9457,7 @@ mod io {
|
||||
impl NewWithNullPtr for wire_cst_prepare_pay_onchain_request {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
receiver_amount_sat: Default::default(),
|
||||
amount: Default::default(),
|
||||
sat_per_vbyte: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
@@ -11123,6 +11215,23 @@ mod io {
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_pay_onchain_amount {
|
||||
tag: i32,
|
||||
kind: PayOnchainAmountKind,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub union PayOnchainAmountKind {
|
||||
Receiver: wire_cst_PayOnchainAmount_Receiver,
|
||||
nil__: (),
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_PayOnchainAmount_Receiver {
|
||||
amount_sat: u64,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_pay_onchain_request {
|
||||
address: *mut wire_cst_list_prim_u_8_strict,
|
||||
prepare_response: wire_cst_prepare_pay_onchain_response,
|
||||
@@ -11265,7 +11374,7 @@ mod io {
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_prepare_pay_onchain_request {
|
||||
receiver_amount_sat: u64,
|
||||
amount: wire_cst_pay_onchain_amount,
|
||||
sat_per_vbyte: *mut u32,
|
||||
}
|
||||
#[repr(C)]
|
||||
|
||||
@@ -307,10 +307,19 @@ pub struct SendPaymentResponse {
|
||||
pub payment: Payment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub enum PayOnchainAmount {
|
||||
/// The amount in satoshi that will be received
|
||||
Receiver { amount_sat: u64 },
|
||||
/// Indicates that all available funds should be sent
|
||||
Drain,
|
||||
}
|
||||
|
||||
/// An argument when calling [crate::sdk::LiquidSdk::prepare_pay_onchain].
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct PreparePayOnchainRequest {
|
||||
pub receiver_amount_sat: u64,
|
||||
pub amount: PayOnchainAmount,
|
||||
/// The optional fee rate of the Bitcoin claim transaction. Defaults to the swapper estimated claim fee.
|
||||
pub sat_per_vbyte: Option<u32>,
|
||||
}
|
||||
|
||||
|
||||
@@ -688,16 +688,24 @@ impl LiquidSdk {
|
||||
.sum())
|
||||
}
|
||||
|
||||
async fn estimate_lockup_tx_fee(&self, amount_sat: u64) -> Result<u64, PaymentError> {
|
||||
fn get_temp_p2tr_addr(&self) -> &str {
|
||||
// TODO Replace this with own address when LWK supports taproot
|
||||
// https://github.com/Blockstream/lwk/issues/31
|
||||
let temp_p2tr_addr = match self.config.network {
|
||||
match self.config.network {
|
||||
LiquidNetwork::Mainnet => "lq1pqvzxvqhrf54dd4sny4cag7497pe38252qefk46t92frs7us8r80ja9ha8r5me09nn22m4tmdqp5p4wafq3s59cql3v9n45t5trwtxrmxfsyxjnstkctj",
|
||||
LiquidNetwork::Testnet => "tlq1pq0wqu32e2xacxeyps22x8gjre4qk3u6r70pj4r62hzczxeyz8x3yxucrpn79zy28plc4x37aaf33kwt6dz2nn6gtkya6h02mwpzy4eh69zzexq7cf5y5"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Estimate the lockup tx fee for Send swaps
|
||||
async fn estimate_lockup_tx_fee_send(
|
||||
&self,
|
||||
user_lockup_amount_sat: u64,
|
||||
) -> Result<u64, PaymentError> {
|
||||
let temp_p2tr_addr = self.get_temp_p2tr_addr();
|
||||
|
||||
self.estimate_onchain_tx_fee(
|
||||
amount_sat,
|
||||
user_lockup_amount_sat,
|
||||
temp_p2tr_addr,
|
||||
self.config
|
||||
.lowball_fee_rate_msat_per_vbyte()
|
||||
@@ -706,6 +714,31 @@ impl LiquidSdk {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Estimate the lockup tx fee for Chain Send swaps
|
||||
async fn estimate_lockup_tx_fee_chain_send(
|
||||
&self,
|
||||
user_lockup_amount_sat: u64,
|
||||
) -> Result<u64, PaymentError> {
|
||||
let temp_p2tr_addr = self.get_temp_p2tr_addr();
|
||||
|
||||
self.estimate_onchain_tx_fee(user_lockup_amount_sat, temp_p2tr_addr, None)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn estimate_drain_tx_fee(&self) -> Result<u64, PaymentError> {
|
||||
let temp_p2tr_addr = self.get_temp_p2tr_addr();
|
||||
|
||||
let fee_sat = self
|
||||
.onchain_wallet
|
||||
.build_drain_tx(None, temp_p2tr_addr, None)
|
||||
.await?
|
||||
.all_fees()
|
||||
.values()
|
||||
.sum();
|
||||
|
||||
Ok(fee_sat)
|
||||
}
|
||||
|
||||
/// Prepares to pay a Lightning invoice via a submarine swap.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -792,7 +825,7 @@ impl LiquidSdk {
|
||||
None => {
|
||||
let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat);
|
||||
let lockup_fees_sat = self
|
||||
.estimate_lockup_tx_fee(receiver_amount_sat + boltz_fees_total)
|
||||
.estimate_lockup_tx_fee_send(receiver_amount_sat + boltz_fees_total)
|
||||
.await?;
|
||||
boltz_fees_total + lockup_fees_sat
|
||||
}
|
||||
@@ -1002,7 +1035,7 @@ impl LiquidSdk {
|
||||
|
||||
let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat);
|
||||
let lockup_tx_fees_sat = self
|
||||
.estimate_lockup_tx_fee(receiver_amount_sat + boltz_fees_total)
|
||||
.estimate_lockup_tx_fee_send(receiver_amount_sat + boltz_fees_total)
|
||||
.await?;
|
||||
ensure_sdk!(
|
||||
fees_sat == boltz_fees_total + lockup_tx_fees_sat,
|
||||
@@ -1142,7 +1175,8 @@ impl LiquidSdk {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req` - the [PreparePayOnchainRequest] containing:
|
||||
/// * `receiver_amount_sat` - the amount in satoshi that will be received
|
||||
/// * `amount` - which can be of two types: [PayOnchainAmount::Drain], which uses all funds,
|
||||
/// and [PayOnchainAmount::Receiver], which sets the amount the receiver should receive
|
||||
/// * `sat_per_vbyte` - the optional fee rate of the Bitcoin claim transaction. Defaults to the swapper estimated claim fee
|
||||
pub async fn prepare_pay_onchain(
|
||||
&self,
|
||||
@@ -1150,7 +1184,7 @@ impl LiquidSdk {
|
||||
) -> Result<PreparePayOnchainResponse, PaymentError> {
|
||||
self.ensure_is_started().await?;
|
||||
|
||||
let receiver_amount_sat = req.receiver_amount_sat;
|
||||
let balance_sat = self.get_info().await?.balance_sat;
|
||||
let pair = self.get_chain_pair(Direction::Outgoing)?;
|
||||
let claim_fees_sat = match req.sat_per_vbyte {
|
||||
Some(sat_per_vbyte) => ESTIMATED_BTC_CLAIM_TX_VSIZE * sat_per_vbyte as u64,
|
||||
@@ -1158,22 +1192,57 @@ impl LiquidSdk {
|
||||
};
|
||||
let server_fees_sat = pair.fees.server();
|
||||
|
||||
let (payer_amount_sat, receiver_amount_sat, total_fees_sat) = match req.amount {
|
||||
PayOnchainAmount::Receiver { amount_sat } => {
|
||||
let receiver_amount_sat = amount_sat;
|
||||
|
||||
let user_lockup_amount_sat_without_service_fee =
|
||||
receiver_amount_sat + claim_fees_sat + server_fees_sat;
|
||||
let boltz_fees_sat = pair.fees.boltz(user_lockup_amount_sat_without_service_fee);
|
||||
let user_lockup_amount_sat = user_lockup_amount_sat_without_service_fee + boltz_fees_sat;
|
||||
|
||||
// The resulting invoice amount contains the service fee, which is rounded up with ceil()
|
||||
// Therefore, when calculating the user_lockup amount, we must also round it up with ceil()
|
||||
let user_lockup_amount_sat = (user_lockup_amount_sat_without_service_fee as f64
|
||||
* 100.0
|
||||
/ (100.0 - pair.fees.percentage))
|
||||
.ceil() as u64;
|
||||
self.validate_user_lockup_amount_for_chain_pair(&pair, user_lockup_amount_sat)?;
|
||||
let lockup_fees_sat = self.estimate_lockup_tx_fee(user_lockup_amount_sat).await?;
|
||||
|
||||
let lockup_fees_sat = self
|
||||
.estimate_lockup_tx_fee_chain_send(user_lockup_amount_sat)
|
||||
.await?;
|
||||
|
||||
let boltz_fees_sat =
|
||||
user_lockup_amount_sat - user_lockup_amount_sat_without_service_fee;
|
||||
let total_fees_sat =
|
||||
boltz_fees_sat + lockup_fees_sat + claim_fees_sat + server_fees_sat;
|
||||
let payer_amount_sat = receiver_amount_sat + total_fees_sat;
|
||||
|
||||
(payer_amount_sat, receiver_amount_sat, total_fees_sat)
|
||||
}
|
||||
PayOnchainAmount::Drain => {
|
||||
let payer_amount_sat = balance_sat;
|
||||
let lockup_fees_sat = self.estimate_drain_tx_fee().await?;
|
||||
|
||||
let user_lockup_amount_sat = payer_amount_sat - lockup_fees_sat;
|
||||
self.validate_user_lockup_amount_for_chain_pair(&pair, user_lockup_amount_sat)?;
|
||||
|
||||
let boltz_fees_sat = pair.fees.boltz(user_lockup_amount_sat);
|
||||
let total_fees_sat =
|
||||
boltz_fees_sat + lockup_fees_sat + claim_fees_sat + server_fees_sat;
|
||||
let receiver_amount_sat = payer_amount_sat - total_fees_sat;
|
||||
|
||||
(payer_amount_sat, receiver_amount_sat, total_fees_sat)
|
||||
}
|
||||
};
|
||||
|
||||
let res = PreparePayOnchainResponse {
|
||||
receiver_amount_sat,
|
||||
claim_fees_sat,
|
||||
total_fees_sat: boltz_fees_sat + lockup_fees_sat + claim_fees_sat + server_fees_sat,
|
||||
total_fees_sat,
|
||||
};
|
||||
|
||||
let payer_amount_sat = res.receiver_amount_sat + res.total_fees_sat;
|
||||
ensure_sdk!(
|
||||
payer_amount_sat <= self.get_info().await?.balance_sat,
|
||||
payer_amount_sat <= balance_sat,
|
||||
PaymentError::InsufficientFunds
|
||||
);
|
||||
|
||||
@@ -1202,6 +1271,7 @@ impl LiquidSdk {
|
||||
) -> Result<SendPaymentResponse, PaymentError> {
|
||||
self.ensure_is_started().await?;
|
||||
|
||||
let balance_sat = self.get_info().await?.balance_sat;
|
||||
let receiver_amount_sat = req.prepare_response.receiver_amount_sat;
|
||||
let pair = self.get_chain_pair(Direction::Outgoing)?;
|
||||
let claim_fees_sat = req.prepare_response.claim_fees_sat;
|
||||
@@ -1210,10 +1280,24 @@ impl LiquidSdk {
|
||||
|
||||
let user_lockup_amount_sat_without_service_fee =
|
||||
receiver_amount_sat + claim_fees_sat + server_fees_sat;
|
||||
let boltz_fee_sat = pair.fees.boltz(user_lockup_amount_sat_without_service_fee);
|
||||
let user_lockup_amount_sat = user_lockup_amount_sat_without_service_fee + boltz_fee_sat;
|
||||
|
||||
// The resulting invoice amount contains the service fee, which is rounded up with ceil()
|
||||
// Therefore, when calculating the user_lockup amount, we must also round it up with ceil()
|
||||
let user_lockup_amount_sat = (user_lockup_amount_sat_without_service_fee as f64 * 100.0
|
||||
/ (100.0 - pair.fees.percentage))
|
||||
.ceil() as u64;
|
||||
let boltz_fee_sat = user_lockup_amount_sat - user_lockup_amount_sat_without_service_fee;
|
||||
self.validate_user_lockup_amount_for_chain_pair(&pair, user_lockup_amount_sat)?;
|
||||
let lockup_fees_sat = self.estimate_lockup_tx_fee(user_lockup_amount_sat).await?;
|
||||
|
||||
let payer_amount_sat = req.prepare_response.total_fees_sat + receiver_amount_sat;
|
||||
|
||||
let lockup_fees_sat = match payer_amount_sat == balance_sat {
|
||||
true => self.estimate_drain_tx_fee().await?,
|
||||
false => {
|
||||
self.estimate_lockup_tx_fee_chain_send(user_lockup_amount_sat)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
ensure_sdk!(
|
||||
req.prepare_response.total_fees_sat
|
||||
@@ -1221,9 +1305,8 @@ impl LiquidSdk {
|
||||
PaymentError::InvalidOrExpiredFees
|
||||
);
|
||||
|
||||
let payer_amount_sat = req.prepare_response.total_fees_sat + receiver_amount_sat;
|
||||
ensure_sdk!(
|
||||
payer_amount_sat <= self.get_info().await?.balance_sat,
|
||||
payer_amount_sat <= balance_sat,
|
||||
PaymentError::InsufficientFunds
|
||||
);
|
||||
|
||||
@@ -1623,17 +1706,17 @@ impl LiquidSdk {
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_chain_swap(
|
||||
async fn create_receive_chain_swap(
|
||||
&self,
|
||||
payer_amount_sat: u64,
|
||||
user_lockup_amount_sat: u64,
|
||||
fees_sat: u64,
|
||||
) -> Result<ChainSwap, PaymentError> {
|
||||
let pair = self.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)?;
|
||||
let pair = self.get_and_validate_chain_pair(Direction::Incoming, user_lockup_amount_sat)?;
|
||||
let claim_fees_sat = pair.fees.claim_estimate();
|
||||
let server_fees_sat = pair.fees.server();
|
||||
|
||||
ensure_sdk!(
|
||||
fees_sat == pair.fees.boltz(payer_amount_sat) + claim_fees_sat + server_fees_sat,
|
||||
fees_sat == pair.fees.boltz(user_lockup_amount_sat) + claim_fees_sat + server_fees_sat,
|
||||
PaymentError::InvalidOrExpiredFees
|
||||
);
|
||||
|
||||
@@ -1665,7 +1748,7 @@ impl LiquidSdk {
|
||||
preimage_hash: preimage.sha256,
|
||||
claim_public_key: Some(claim_public_key),
|
||||
refund_public_key: Some(refund_public_key),
|
||||
user_lock_amount: Some(payer_amount_sat as u32), // TODO update our model
|
||||
user_lock_amount: Some(user_lockup_amount_sat as u32), // TODO update our model
|
||||
server_lock_amount: None,
|
||||
pair_hash: Some(pair.hash),
|
||||
referral_id: None,
|
||||
@@ -1676,8 +1759,8 @@ impl LiquidSdk {
|
||||
let create_response_json =
|
||||
ChainSwap::from_boltz_struct_to_json(&create_response, &swap_id)?;
|
||||
|
||||
let accept_zero_conf = payer_amount_sat <= pair.limits.maximal_zero_conf;
|
||||
let receiver_amount_sat = payer_amount_sat - fees_sat;
|
||||
let accept_zero_conf = user_lockup_amount_sat <= pair.limits.maximal_zero_conf;
|
||||
let receiver_amount_sat = user_lockup_amount_sat - fees_sat;
|
||||
let claim_address = self.onchain_wallet.next_unused_address().await?.to_string();
|
||||
|
||||
let swap = ChainSwap {
|
||||
@@ -1688,7 +1771,7 @@ impl LiquidSdk {
|
||||
timeout_block_height: create_response.lockup_details.timeout_block_height,
|
||||
preimage: preimage_str,
|
||||
description: Some("Bitcoin transfer".to_string()),
|
||||
payer_amount_sat,
|
||||
payer_amount_sat: user_lockup_amount_sat,
|
||||
receiver_amount_sat,
|
||||
claim_fees_sat,
|
||||
accept_zero_conf,
|
||||
@@ -1715,7 +1798,9 @@ impl LiquidSdk {
|
||||
) -> Result<ReceivePaymentResponse, PaymentError> {
|
||||
self.ensure_is_started().await?;
|
||||
|
||||
let swap = self.create_chain_swap(payer_amount_sat, fees_sat).await?;
|
||||
let swap = self
|
||||
.create_receive_chain_swap(payer_amount_sat, fees_sat)
|
||||
.await?;
|
||||
let create_response = swap.get_boltz_create_response()?;
|
||||
let address = create_response.lockup_details.lockup_address;
|
||||
|
||||
@@ -1836,7 +1921,7 @@ impl LiquidSdk {
|
||||
/// * `redirect_url` - the optional redirect URL the provider should redirect to after purchase
|
||||
pub async fn buy_bitcoin(&self, req: &BuyBitcoinRequest) -> Result<String, PaymentError> {
|
||||
let swap = self
|
||||
.create_chain_swap(
|
||||
.create_receive_chain_swap(
|
||||
req.prepare_response.amount_sat,
|
||||
req.prepare_response.fees_sat,
|
||||
)
|
||||
|
||||
@@ -40,6 +40,15 @@ impl OnchainWallet for MockWallet {
|
||||
Ok(TEST_LIQUID_TX.clone())
|
||||
}
|
||||
|
||||
async fn build_drain_tx(
|
||||
&self,
|
||||
_fee_rate_sats_per_kvb: Option<f32>,
|
||||
_recipient_address: &str,
|
||||
_enforce_amount_sat: Option<u64>,
|
||||
) -> Result<Transaction, PaymentError> {
|
||||
Ok(TEST_LIQUID_TX.clone())
|
||||
}
|
||||
|
||||
async fn next_unused_address(&self) -> Result<Address, PaymentError> {
|
||||
Ok(TEST_P2TR_ADDR.clone())
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use sdk_common::lightning::util::message_signing::verify;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
ensure_sdk,
|
||||
error::PaymentError,
|
||||
model::{Config, LiquidNetwork},
|
||||
};
|
||||
@@ -38,6 +39,20 @@ pub trait OnchainWallet: Send + Sync {
|
||||
amount_sat: u64,
|
||||
) -> Result<Transaction, PaymentError>;
|
||||
|
||||
/// Builds a drain tx.
|
||||
///
|
||||
/// ### Arguments
|
||||
/// - `fee_rate_sats_per_kvb`: custom drain tx feerate
|
||||
/// - `recipient_address`: drain tx recipient
|
||||
/// - `enforce_amount_sat`: if set, the drain tx will only be built if the amount transferred is
|
||||
/// this amount, otherwise it will fail with a validation error
|
||||
async fn build_drain_tx(
|
||||
&self,
|
||||
fee_rate_sats_per_kvb: Option<f32>,
|
||||
recipient_address: &str,
|
||||
enforce_amount_sat: Option<u64>,
|
||||
) -> Result<Transaction, PaymentError>;
|
||||
|
||||
/// Get the next unused address in the wallet
|
||||
async fn next_unused_address(&self) -> Result<Address, PaymentError>;
|
||||
|
||||
@@ -139,6 +154,49 @@ impl OnchainWallet for LiquidOnchainWallet {
|
||||
Ok(lwk_wollet.finalize(&mut pset)?)
|
||||
}
|
||||
|
||||
async fn build_drain_tx(
|
||||
&self,
|
||||
fee_rate_sats_per_kvb: Option<f32>,
|
||||
recipient_address: &str,
|
||||
enforce_amount_sat: Option<u64>,
|
||||
) -> Result<Transaction, PaymentError> {
|
||||
let lwk_wollet = self.wallet.lock().await;
|
||||
|
||||
let address =
|
||||
ElementsAddress::from_str(recipient_address).map_err(|e| PaymentError::Generic {
|
||||
err: format!(
|
||||
"Recipient address {recipient_address} is not a valid ElementsAddress: {e:?}"
|
||||
),
|
||||
})?;
|
||||
let mut pset = lwk_wollet
|
||||
.tx_builder()
|
||||
.drain_lbtc_wallet()
|
||||
.drain_lbtc_to(address)
|
||||
.fee_rate(fee_rate_sats_per_kvb)
|
||||
.finish()?;
|
||||
|
||||
if let Some(enforce_amount_sat) = enforce_amount_sat {
|
||||
let pset_details = lwk_wollet.get_details(&pset)?;
|
||||
let pset_balance_sat = pset_details
|
||||
.balance
|
||||
.balances
|
||||
.get(&lwk_wollet.policy_asset())
|
||||
.unwrap_or(&0);
|
||||
let pset_fees = pset_details.balance.fee;
|
||||
|
||||
ensure_sdk!(
|
||||
(*pset_balance_sat * -1) as u64 - pset_fees == enforce_amount_sat,
|
||||
PaymentError::Generic {
|
||||
err: format!("Drain tx amount {pset_balance_sat} sat doesn't match enforce_amount_sat {enforce_amount_sat} sat")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let signer = AnySigner::Software(self.lwk_signer.clone());
|
||||
signer.sign(&mut pset)?;
|
||||
Ok(lwk_wollet.finalize(&mut pset)?)
|
||||
}
|
||||
|
||||
/// Get the next unused address in the wallet
|
||||
async fn next_unused_address(&self) -> Result<Address, PaymentError> {
|
||||
Ok(self.wallet.lock().await.address(None)?.address().clone())
|
||||
|
||||
@@ -2199,6 +2199,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return raw == null ? null : dco_decode_list_payment_type(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
PayOnchainAmount dco_decode_pay_onchain_amount(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
switch (raw[0]) {
|
||||
case 0:
|
||||
return PayOnchainAmount_Receiver(
|
||||
amountSat: dco_decode_u_64(raw[1]),
|
||||
);
|
||||
case 1:
|
||||
return PayOnchainAmount_Drain();
|
||||
default:
|
||||
throw Exception("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
PayOnchainRequest dco_decode_pay_onchain_request(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -2376,7 +2391,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
|
||||
return PreparePayOnchainRequest(
|
||||
receiverAmountSat: dco_decode_u_64(arr[0]),
|
||||
amount: dco_decode_pay_onchain_amount(arr[0]),
|
||||
satPerVbyte: dco_decode_opt_box_autoadd_u_32(arr[1]),
|
||||
);
|
||||
}
|
||||
@@ -3888,6 +3903,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
PayOnchainAmount sse_decode_pay_onchain_amount(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var tag_ = sse_decode_i_32(deserializer);
|
||||
switch (tag_) {
|
||||
case 0:
|
||||
var var_amountSat = sse_decode_u_64(deserializer);
|
||||
return PayOnchainAmount_Receiver(amountSat: var_amountSat);
|
||||
case 1:
|
||||
return PayOnchainAmount_Drain();
|
||||
default:
|
||||
throw UnimplementedError('');
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
PayOnchainRequest sse_decode_pay_onchain_request(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -4062,9 +4093,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
@protected
|
||||
PreparePayOnchainRequest sse_decode_prepare_pay_onchain_request(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_receiverAmountSat = sse_decode_u_64(deserializer);
|
||||
var var_amount = sse_decode_pay_onchain_amount(deserializer);
|
||||
var var_satPerVbyte = sse_decode_opt_box_autoadd_u_32(deserializer);
|
||||
return PreparePayOnchainRequest(receiverAmountSat: var_receiverAmountSat, satPerVbyte: var_satPerVbyte);
|
||||
return PreparePayOnchainRequest(amount: var_amount, satPerVbyte: var_satPerVbyte);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -5487,6 +5518,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_pay_onchain_amount(PayOnchainAmount self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
switch (self) {
|
||||
case PayOnchainAmount_Receiver(amountSat: final amountSat):
|
||||
sse_encode_i_32(0, serializer);
|
||||
sse_encode_u_64(amountSat, serializer);
|
||||
case PayOnchainAmount_Drain():
|
||||
sse_encode_i_32(1, serializer);
|
||||
default:
|
||||
throw UnimplementedError('');
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_pay_onchain_request(PayOnchainRequest self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -5644,7 +5689,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
@protected
|
||||
void sse_encode_prepare_pay_onchain_request(PreparePayOnchainRequest self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_64(self.receiverAmountSat, serializer);
|
||||
sse_encode_pay_onchain_amount(self.amount, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(self.satPerVbyte, serializer);
|
||||
}
|
||||
|
||||
|
||||
@@ -365,6 +365,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<PaymentType>? dco_decode_opt_list_payment_type(dynamic raw);
|
||||
|
||||
@protected
|
||||
PayOnchainAmount dco_decode_pay_onchain_amount(dynamic raw);
|
||||
|
||||
@protected
|
||||
PayOnchainRequest dco_decode_pay_onchain_request(dynamic raw);
|
||||
|
||||
@@ -837,6 +840,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<PaymentType>? sse_decode_opt_list_payment_type(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PayOnchainAmount sse_decode_pay_onchain_amount(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PayOnchainRequest sse_decode_pay_onchain_request(SseDeserializer deserializer);
|
||||
|
||||
@@ -2253,6 +2259,20 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
cst_api_fill_to_wire_limits(apiObj.receive, wireObj.receive);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_pay_onchain_amount(PayOnchainAmount apiObj, wire_cst_pay_onchain_amount wireObj) {
|
||||
if (apiObj is PayOnchainAmount_Receiver) {
|
||||
var pre_amount_sat = cst_encode_u_64(apiObj.amountSat);
|
||||
wireObj.tag = 0;
|
||||
wireObj.kind.Receiver.amount_sat = pre_amount_sat;
|
||||
return;
|
||||
}
|
||||
if (apiObj is PayOnchainAmount_Drain) {
|
||||
wireObj.tag = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_pay_onchain_request(
|
||||
PayOnchainRequest apiObj, wire_cst_pay_onchain_request wireObj) {
|
||||
@@ -2440,7 +2460,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void cst_api_fill_to_wire_prepare_pay_onchain_request(
|
||||
PreparePayOnchainRequest apiObj, wire_cst_prepare_pay_onchain_request wireObj) {
|
||||
wireObj.receiver_amount_sat = cst_encode_u_64(apiObj.receiverAmountSat);
|
||||
cst_api_fill_to_wire_pay_onchain_amount(apiObj.amount, wireObj.amount);
|
||||
wireObj.sat_per_vbyte = cst_encode_opt_box_autoadd_u_32(apiObj.satPerVbyte);
|
||||
}
|
||||
|
||||
@@ -3114,6 +3134,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_opt_list_payment_type(List<PaymentType>? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_pay_onchain_amount(PayOnchainAmount self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_pay_onchain_request(PayOnchainRequest self, SseSerializer serializer);
|
||||
|
||||
@@ -4734,9 +4757,24 @@ final class wire_cst_prepare_buy_bitcoin_request extends ffi.Struct {
|
||||
external int amount_sat;
|
||||
}
|
||||
|
||||
final class wire_cst_prepare_pay_onchain_request extends ffi.Struct {
|
||||
final class wire_cst_PayOnchainAmount_Receiver extends ffi.Struct {
|
||||
@ffi.Uint64()
|
||||
external int receiver_amount_sat;
|
||||
external int amount_sat;
|
||||
}
|
||||
|
||||
final class PayOnchainAmountKind extends ffi.Union {
|
||||
external wire_cst_PayOnchainAmount_Receiver Receiver;
|
||||
}
|
||||
|
||||
final class wire_cst_pay_onchain_amount extends ffi.Struct {
|
||||
@ffi.Int32()
|
||||
external int tag;
|
||||
|
||||
external PayOnchainAmountKind kind;
|
||||
}
|
||||
|
||||
final class wire_cst_prepare_pay_onchain_request extends ffi.Struct {
|
||||
external wire_cst_pay_onchain_amount amount;
|
||||
|
||||
external ffi.Pointer<ffi.Uint32> sat_per_vbyte;
|
||||
}
|
||||
|
||||
@@ -405,6 +405,19 @@ class OnchainPaymentLimitsResponse {
|
||||
receive == other.receive;
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class PayOnchainAmount with _$PayOnchainAmount {
|
||||
const PayOnchainAmount._();
|
||||
|
||||
/// The amount in satoshi that will be received
|
||||
const factory PayOnchainAmount.receiver({
|
||||
required BigInt amountSat,
|
||||
}) = PayOnchainAmount_Receiver;
|
||||
|
||||
/// Indicates that all available funds should be sent
|
||||
const factory PayOnchainAmount.drain() = PayOnchainAmount_Drain;
|
||||
}
|
||||
|
||||
/// An argument when calling [crate::sdk::LiquidSdk::pay_onchain].
|
||||
class PayOnchainRequest {
|
||||
final String address;
|
||||
@@ -695,23 +708,25 @@ class PrepareBuyBitcoinResponse {
|
||||
|
||||
/// An argument when calling [crate::sdk::LiquidSdk::prepare_pay_onchain].
|
||||
class PreparePayOnchainRequest {
|
||||
final BigInt receiverAmountSat;
|
||||
final PayOnchainAmount amount;
|
||||
|
||||
/// The optional fee rate of the Bitcoin claim transaction. Defaults to the swapper estimated claim fee.
|
||||
final int? satPerVbyte;
|
||||
|
||||
const PreparePayOnchainRequest({
|
||||
required this.receiverAmountSat,
|
||||
required this.amount,
|
||||
this.satPerVbyte,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => receiverAmountSat.hashCode ^ satPerVbyte.hashCode;
|
||||
int get hashCode => amount.hashCode ^ satPerVbyte.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PreparePayOnchainRequest &&
|
||||
runtimeType == other.runtimeType &&
|
||||
receiverAmountSat == other.receiverAmountSat &&
|
||||
amount == other.amount &&
|
||||
satPerVbyte == other.satPerVbyte;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +283,153 @@ abstract class LnUrlPayResult_PayError extends LnUrlPayResult {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PayOnchainAmount {}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $PayOnchainAmountCopyWith<$Res> {
|
||||
factory $PayOnchainAmountCopyWith(PayOnchainAmount value, $Res Function(PayOnchainAmount) then) =
|
||||
_$PayOnchainAmountCopyWithImpl<$Res, PayOnchainAmount>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$PayOnchainAmountCopyWithImpl<$Res, $Val extends PayOnchainAmount>
|
||||
implements $PayOnchainAmountCopyWith<$Res> {
|
||||
_$PayOnchainAmountCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of PayOnchainAmount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$PayOnchainAmount_ReceiverImplCopyWith<$Res> {
|
||||
factory _$$PayOnchainAmount_ReceiverImplCopyWith(
|
||||
_$PayOnchainAmount_ReceiverImpl value, $Res Function(_$PayOnchainAmount_ReceiverImpl) then) =
|
||||
__$$PayOnchainAmount_ReceiverImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({BigInt amountSat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$PayOnchainAmount_ReceiverImplCopyWithImpl<$Res>
|
||||
extends _$PayOnchainAmountCopyWithImpl<$Res, _$PayOnchainAmount_ReceiverImpl>
|
||||
implements _$$PayOnchainAmount_ReceiverImplCopyWith<$Res> {
|
||||
__$$PayOnchainAmount_ReceiverImplCopyWithImpl(
|
||||
_$PayOnchainAmount_ReceiverImpl _value, $Res Function(_$PayOnchainAmount_ReceiverImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of PayOnchainAmount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? amountSat = null,
|
||||
}) {
|
||||
return _then(_$PayOnchainAmount_ReceiverImpl(
|
||||
amountSat: null == amountSat
|
||||
? _value.amountSat
|
||||
: amountSat // ignore: cast_nullable_to_non_nullable
|
||||
as BigInt,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$PayOnchainAmount_ReceiverImpl extends PayOnchainAmount_Receiver {
|
||||
const _$PayOnchainAmount_ReceiverImpl({required this.amountSat}) : super._();
|
||||
|
||||
@override
|
||||
final BigInt amountSat;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PayOnchainAmount.receiver(amountSat: $amountSat)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PayOnchainAmount_ReceiverImpl &&
|
||||
(identical(other.amountSat, amountSat) || other.amountSat == amountSat));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, amountSat);
|
||||
|
||||
/// Create a copy of PayOnchainAmount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$PayOnchainAmount_ReceiverImplCopyWith<_$PayOnchainAmount_ReceiverImpl> get copyWith =>
|
||||
__$$PayOnchainAmount_ReceiverImplCopyWithImpl<_$PayOnchainAmount_ReceiverImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class PayOnchainAmount_Receiver extends PayOnchainAmount {
|
||||
const factory PayOnchainAmount_Receiver({required final BigInt amountSat}) =
|
||||
_$PayOnchainAmount_ReceiverImpl;
|
||||
const PayOnchainAmount_Receiver._() : super._();
|
||||
|
||||
BigInt get amountSat;
|
||||
|
||||
/// Create a copy of PayOnchainAmount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$PayOnchainAmount_ReceiverImplCopyWith<_$PayOnchainAmount_ReceiverImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$PayOnchainAmount_DrainImplCopyWith<$Res> {
|
||||
factory _$$PayOnchainAmount_DrainImplCopyWith(
|
||||
_$PayOnchainAmount_DrainImpl value, $Res Function(_$PayOnchainAmount_DrainImpl) then) =
|
||||
__$$PayOnchainAmount_DrainImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$PayOnchainAmount_DrainImplCopyWithImpl<$Res>
|
||||
extends _$PayOnchainAmountCopyWithImpl<$Res, _$PayOnchainAmount_DrainImpl>
|
||||
implements _$$PayOnchainAmount_DrainImplCopyWith<$Res> {
|
||||
__$$PayOnchainAmount_DrainImplCopyWithImpl(
|
||||
_$PayOnchainAmount_DrainImpl _value, $Res Function(_$PayOnchainAmount_DrainImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of PayOnchainAmount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$PayOnchainAmount_DrainImpl extends PayOnchainAmount_Drain {
|
||||
const _$PayOnchainAmount_DrainImpl() : super._();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PayOnchainAmount.drain()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$PayOnchainAmount_DrainImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
}
|
||||
|
||||
abstract class PayOnchainAmount_Drain extends PayOnchainAmount {
|
||||
const factory PayOnchainAmount_Drain() = _$PayOnchainAmount_DrainImpl;
|
||||
const PayOnchainAmount_Drain._() : super._();
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PaymentDetails {
|
||||
/// Represents the invoice description
|
||||
|
||||
@@ -1575,9 +1575,24 @@ final class wire_cst_prepare_buy_bitcoin_request extends ffi.Struct {
|
||||
external int amount_sat;
|
||||
}
|
||||
|
||||
final class wire_cst_prepare_pay_onchain_request extends ffi.Struct {
|
||||
final class wire_cst_PayOnchainAmount_Receiver extends ffi.Struct {
|
||||
@ffi.Uint64()
|
||||
external int receiver_amount_sat;
|
||||
external int amount_sat;
|
||||
}
|
||||
|
||||
final class PayOnchainAmountKind extends ffi.Union {
|
||||
external wire_cst_PayOnchainAmount_Receiver Receiver;
|
||||
}
|
||||
|
||||
final class wire_cst_pay_onchain_amount extends ffi.Struct {
|
||||
@ffi.Int32()
|
||||
external int tag;
|
||||
|
||||
external PayOnchainAmountKind kind;
|
||||
}
|
||||
|
||||
final class wire_cst_prepare_pay_onchain_request extends ffi.Struct {
|
||||
external wire_cst_pay_onchain_amount amount;
|
||||
|
||||
external ffi.Pointer<ffi.Uint32> sat_per_vbyte;
|
||||
}
|
||||
|
||||
@@ -1330,13 +1330,13 @@ fun asPreparePayOnchainRequest(preparePayOnchainRequest: ReadableMap): PreparePa
|
||||
if (!validateMandatoryFields(
|
||||
preparePayOnchainRequest,
|
||||
arrayOf(
|
||||
"receiverAmountSat",
|
||||
"amount",
|
||||
),
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val receiverAmountSat = preparePayOnchainRequest.getDouble("receiverAmountSat").toULong()
|
||||
val amount = preparePayOnchainRequest.getMap("amount")?.let { asPayOnchainAmount(it) }!!
|
||||
val satPerVbyte =
|
||||
if (hasNonNullKey(
|
||||
preparePayOnchainRequest,
|
||||
@@ -1347,12 +1347,12 @@ fun asPreparePayOnchainRequest(preparePayOnchainRequest: ReadableMap): PreparePa
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return PreparePayOnchainRequest(receiverAmountSat, satPerVbyte)
|
||||
return PreparePayOnchainRequest(amount, satPerVbyte)
|
||||
}
|
||||
|
||||
fun readableMapOf(preparePayOnchainRequest: PreparePayOnchainRequest): ReadableMap =
|
||||
readableMapOf(
|
||||
"receiverAmountSat" to preparePayOnchainRequest.receiverAmountSat,
|
||||
"amount" to readableMapOf(preparePayOnchainRequest.amount),
|
||||
"satPerVbyte" to preparePayOnchainRequest.satPerVbyte,
|
||||
)
|
||||
|
||||
@@ -2485,6 +2485,44 @@ fun asNetworkList(arr: ReadableArray): List<Network> {
|
||||
return list
|
||||
}
|
||||
|
||||
fun asPayOnchainAmount(payOnchainAmount: ReadableMap): PayOnchainAmount? {
|
||||
val type = payOnchainAmount.getString("type")
|
||||
|
||||
if (type == "receiver") {
|
||||
val amountSat = payOnchainAmount.getDouble("amountSat").toULong()
|
||||
return PayOnchainAmount.Receiver(amountSat)
|
||||
}
|
||||
if (type == "drain") {
|
||||
return PayOnchainAmount.Drain
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun readableMapOf(payOnchainAmount: PayOnchainAmount): ReadableMap? {
|
||||
val map = Arguments.createMap()
|
||||
when (payOnchainAmount) {
|
||||
is PayOnchainAmount.Receiver -> {
|
||||
pushToMap(map, "type", "receiver")
|
||||
pushToMap(map, "amountSat", payOnchainAmount.amountSat)
|
||||
}
|
||||
is PayOnchainAmount.Drain -> {
|
||||
pushToMap(map, "type", "drain")
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
fun asPayOnchainAmountList(arr: ReadableArray): List<PayOnchainAmount> {
|
||||
val list = ArrayList<PayOnchainAmount>()
|
||||
for (value in arr.toList()) {
|
||||
when (value) {
|
||||
is ReadableMap -> list.add(asPayOnchainAmount(value)!!)
|
||||
else -> throw SdkException.Generic(errUnexpectedType(value))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? {
|
||||
val type = paymentDetails.getString("type")
|
||||
|
||||
|
||||
@@ -1571,9 +1571,11 @@ enum BreezSDKLiquidMapper {
|
||||
}
|
||||
|
||||
static func asPreparePayOnchainRequest(preparePayOnchainRequest: [String: Any?]) throws -> PreparePayOnchainRequest {
|
||||
guard let receiverAmountSat = preparePayOnchainRequest["receiverAmountSat"] as? UInt64 else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "receiverAmountSat", typeName: "PreparePayOnchainRequest"))
|
||||
guard let amountTmp = preparePayOnchainRequest["amount"] as? [String: Any?] else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amount", typeName: "PreparePayOnchainRequest"))
|
||||
}
|
||||
let amount = try asPayOnchainAmount(payOnchainAmount: amountTmp)
|
||||
|
||||
var satPerVbyte: UInt32?
|
||||
if hasNonNilKey(data: preparePayOnchainRequest, key: "satPerVbyte") {
|
||||
guard let satPerVbyteTmp = preparePayOnchainRequest["satPerVbyte"] as? UInt32 else {
|
||||
@@ -1582,12 +1584,12 @@ enum BreezSDKLiquidMapper {
|
||||
satPerVbyte = satPerVbyteTmp
|
||||
}
|
||||
|
||||
return PreparePayOnchainRequest(receiverAmountSat: receiverAmountSat, satPerVbyte: satPerVbyte)
|
||||
return PreparePayOnchainRequest(amount: amount, satPerVbyte: satPerVbyte)
|
||||
}
|
||||
|
||||
static func dictionaryOf(preparePayOnchainRequest: PreparePayOnchainRequest) -> [String: Any?] {
|
||||
return [
|
||||
"receiverAmountSat": preparePayOnchainRequest.receiverAmountSat,
|
||||
"amount": dictionaryOf(payOnchainAmount: preparePayOnchainRequest.amount),
|
||||
"satPerVbyte": preparePayOnchainRequest.satPerVbyte == nil ? nil : preparePayOnchainRequest.satPerVbyte,
|
||||
]
|
||||
}
|
||||
@@ -3065,6 +3067,55 @@ enum BreezSDKLiquidMapper {
|
||||
return list
|
||||
}
|
||||
|
||||
static func asPayOnchainAmount(payOnchainAmount: [String: Any?]) throws -> PayOnchainAmount {
|
||||
let type = payOnchainAmount["type"] as! String
|
||||
if type == "receiver" {
|
||||
guard let _amountSat = payOnchainAmount["amountSat"] as? UInt64 else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amountSat", typeName: "PayOnchainAmount"))
|
||||
}
|
||||
return PayOnchainAmount.receiver(amountSat: _amountSat)
|
||||
}
|
||||
if type == "drain" {
|
||||
return PayOnchainAmount.drain
|
||||
}
|
||||
|
||||
throw SdkError.Generic(message: "Unexpected type \(type) for enum PayOnchainAmount")
|
||||
}
|
||||
|
||||
static func dictionaryOf(payOnchainAmount: PayOnchainAmount) -> [String: Any?] {
|
||||
switch payOnchainAmount {
|
||||
case let .receiver(
|
||||
amountSat
|
||||
):
|
||||
return [
|
||||
"type": "receiver",
|
||||
"amountSat": amountSat,
|
||||
]
|
||||
|
||||
case .drain:
|
||||
return [
|
||||
"type": "drain",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
static func arrayOf(payOnchainAmountList: [PayOnchainAmount]) -> [Any] {
|
||||
return payOnchainAmountList.map { v -> [String: Any?] in return dictionaryOf(payOnchainAmount: v) }
|
||||
}
|
||||
|
||||
static func asPayOnchainAmountList(arr: [Any]) throws -> [PayOnchainAmount] {
|
||||
var list = [PayOnchainAmount]()
|
||||
for value in arr {
|
||||
if let val = value as? [String: Any?] {
|
||||
var payOnchainAmount = try asPayOnchainAmount(payOnchainAmount: val)
|
||||
list.append(payOnchainAmount)
|
||||
} else {
|
||||
throw SdkError.Generic(message: errUnexpectedType(typeName: "PayOnchainAmount"))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
static func asPaymentDetails(paymentDetails: [String: Any?]) throws -> PaymentDetails {
|
||||
let type = paymentDetails["type"] as! String
|
||||
if type == "lightning" {
|
||||
|
||||
@@ -244,7 +244,7 @@ export interface PrepareBuyBitcoinResponse {
|
||||
}
|
||||
|
||||
export interface PreparePayOnchainRequest {
|
||||
receiverAmountSat: number
|
||||
amount: PayOnchainAmount
|
||||
satPerVbyte?: number
|
||||
}
|
||||
|
||||
@@ -489,6 +489,18 @@ export enum Network {
|
||||
REGTEST = "regtest"
|
||||
}
|
||||
|
||||
export enum PayOnchainAmountVariant {
|
||||
RECEIVER = "receiver",
|
||||
DRAIN = "drain"
|
||||
}
|
||||
|
||||
export type PayOnchainAmount = {
|
||||
type: PayOnchainAmountVariant.RECEIVER,
|
||||
amountSat: number
|
||||
} | {
|
||||
type: PayOnchainAmountVariant.DRAIN
|
||||
}
|
||||
|
||||
export enum PaymentDetailsVariant {
|
||||
LIGHTNING = "lightning",
|
||||
LIQUID = "liquid",
|
||||
|
||||
Reference in New Issue
Block a user