mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-24 01:14:22 +01:00
feat: add zero-conf checks when receive lockup is in the mempool (#292)
This commit is contained in:
@@ -14,6 +14,18 @@ void store_dart_post_cobject(DartPostCObjectFnType ptr);
|
||||
// EXTRA END
|
||||
typedef struct _Dart_Handle* Dart_Handle;
|
||||
|
||||
/**
|
||||
* The minimum acceptable fee rate when claiming using zero-conf
|
||||
*/
|
||||
#define DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET 0.1
|
||||
|
||||
#define DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET 0.01
|
||||
|
||||
/**
|
||||
* The maximum acceptable amount in satoshi when claiming using zero-conf
|
||||
*/
|
||||
#define DEFAULT_ZERO_CONF_MAX_SAT 100000
|
||||
|
||||
typedef struct wire_cst_list_prim_u_8_strict {
|
||||
uint8_t *ptr;
|
||||
int32_t len;
|
||||
@@ -106,6 +118,8 @@ typedef struct wire_cst_config {
|
||||
struct wire_cst_list_prim_u_8_strict *working_dir;
|
||||
int32_t network;
|
||||
uint64_t payment_timeout_sec;
|
||||
float zero_conf_min_fee_rate;
|
||||
uint64_t *zero_conf_max_amount_sat;
|
||||
} wire_cst_config;
|
||||
|
||||
typedef struct wire_cst_connect_request {
|
||||
|
||||
@@ -33,6 +33,8 @@ dictionary Config {
|
||||
string working_dir;
|
||||
Network network;
|
||||
u64 payment_timeout_sec;
|
||||
f32 zero_conf_min_fee_rate;
|
||||
u64? zero_conf_max_amount_sat;
|
||||
};
|
||||
|
||||
enum Network {
|
||||
|
||||
@@ -182,6 +182,8 @@ impl CstDecode<crate::model::Config> for wire_cst_config {
|
||||
working_dir: self.working_dir.cst_decode(),
|
||||
network: self.network.cst_decode(),
|
||||
payment_timeout_sec: self.payment_timeout_sec.cst_decode(),
|
||||
zero_conf_min_fee_rate: self.zero_conf_min_fee_rate.cst_decode(),
|
||||
zero_conf_max_amount_sat: self.zero_conf_max_amount_sat.cst_decode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,6 +527,8 @@ impl NewWithNullPtr for wire_cst_config {
|
||||
working_dir: core::ptr::null_mut(),
|
||||
network: Default::default(),
|
||||
payment_timeout_sec: Default::default(),
|
||||
zero_conf_min_fee_rate: Default::default(),
|
||||
zero_conf_max_amount_sat: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1083,6 +1087,8 @@ pub struct wire_cst_config {
|
||||
working_dir: *mut wire_cst_list_prim_u_8_strict,
|
||||
network: i32,
|
||||
payment_timeout_sec: u64,
|
||||
zero_conf_min_fee_rate: f32,
|
||||
zero_conf_max_amount_sat: *mut u64,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -633,6 +633,12 @@ fn wire__crate__bindings__parse_invoice_impl(
|
||||
|
||||
// Section: dart2rust
|
||||
|
||||
impl CstDecode<f32> for f32 {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> f32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl CstDecode<i32> for i32 {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> i32 {
|
||||
@@ -781,12 +787,16 @@ impl SseDecode for crate::model::Config {
|
||||
let mut var_workingDir = <String>::sse_decode(deserializer);
|
||||
let mut var_network = <crate::model::Network>::sse_decode(deserializer);
|
||||
let mut var_paymentTimeoutSec = <u64>::sse_decode(deserializer);
|
||||
let mut var_zeroConfMinFeeRate = <f32>::sse_decode(deserializer);
|
||||
let mut var_zeroConfMaxAmountSat = <Option<u64>>::sse_decode(deserializer);
|
||||
return crate::model::Config {
|
||||
boltz_url: var_boltzUrl,
|
||||
electrum_url: var_electrumUrl,
|
||||
working_dir: var_workingDir,
|
||||
network: var_network,
|
||||
payment_timeout_sec: var_paymentTimeoutSec,
|
||||
zero_conf_min_fee_rate: var_zeroConfMinFeeRate,
|
||||
zero_conf_max_amount_sat: var_zeroConfMaxAmountSat,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -803,6 +813,13 @@ impl SseDecode for crate::model::ConnectRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for f32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_f32::<NativeEndian>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::GetInfoResponse {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -1385,6 +1402,8 @@ impl flutter_rust_bridge::IntoDart for crate::model::Config {
|
||||
self.working_dir.into_into_dart().into_dart(),
|
||||
self.network.into_into_dart().into_dart(),
|
||||
self.payment_timeout_sec.into_into_dart().into_dart(),
|
||||
self.zero_conf_min_fee_rate.into_into_dart().into_dart(),
|
||||
self.zero_conf_max_amount_sat.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
@@ -1899,6 +1918,8 @@ impl SseEncode for crate::model::Config {
|
||||
<String>::sse_encode(self.working_dir, serializer);
|
||||
<crate::model::Network>::sse_encode(self.network, serializer);
|
||||
<u64>::sse_encode(self.payment_timeout_sec, serializer);
|
||||
<f32>::sse_encode(self.zero_conf_min_fee_rate, serializer);
|
||||
<Option<u64>>::sse_encode(self.zero_conf_max_amount_sat, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1910,6 +1931,13 @@ impl SseEncode for crate::model::ConnectRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for f32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_f32::<NativeEndian>(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::GetInfoResponse {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
|
||||
@@ -11,6 +11,10 @@ use rusqlite::ToSql;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::PaymentError;
|
||||
use crate::receive_swap::{
|
||||
DEFAULT_ZERO_CONF_MAX_SAT, DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET,
|
||||
DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET,
|
||||
};
|
||||
use crate::utils;
|
||||
|
||||
/// Configuration for the Liquid SDK
|
||||
@@ -25,7 +29,13 @@ pub struct Config {
|
||||
pub network: Network,
|
||||
/// Send payment timeout. See [crate::sdk::LiquidSdk::send_payment]
|
||||
pub payment_timeout_sec: u64,
|
||||
/// Zero-conf minimum accepted fee-rate in sat/vbyte
|
||||
pub zero_conf_min_fee_rate: f32,
|
||||
/// Maximum amount in satoshi to accept zero-conf payments with
|
||||
/// Defaults to [crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT]
|
||||
pub zero_conf_max_amount_sat: Option<u64>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn mainnet() -> Self {
|
||||
Config {
|
||||
@@ -34,6 +44,8 @@ impl Config {
|
||||
working_dir: ".".to_string(),
|
||||
network: Network::Mainnet,
|
||||
payment_timeout_sec: 15,
|
||||
zero_conf_min_fee_rate: DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET,
|
||||
zero_conf_max_amount_sat: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +56,15 @@ impl Config {
|
||||
working_dir: ".".to_string(),
|
||||
network: Network::Testnet,
|
||||
payment_timeout_sec: 15,
|
||||
zero_conf_min_fee_rate: DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET,
|
||||
zero_conf_max_amount_sat: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero_conf_max_amount_sat(&self) -> u64 {
|
||||
self.zero_conf_max_amount_sat
|
||||
.unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize)]
|
||||
|
||||
@@ -6,15 +6,22 @@ use boltz_client::swaps::boltzv2;
|
||||
use log::{debug, error, info, warn};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::ensure_sdk;
|
||||
use crate::model::PaymentState::{Complete, Created, Failed, Pending, TimedOut};
|
||||
use crate::model::{PaymentTxData, PaymentType, ReceiveSwap};
|
||||
use crate::model::{Config, PaymentTxData, PaymentType, ReceiveSwap};
|
||||
use crate::{ensure_sdk, utils};
|
||||
use crate::{
|
||||
error::PaymentError, model::PaymentState, persist::Persister, swapper::Swapper,
|
||||
wallet::OnchainWallet,
|
||||
};
|
||||
|
||||
/// The minimum acceptable fee rate when claiming using zero-conf
|
||||
pub const DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET: f32 = 0.1;
|
||||
pub const DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET: f32 = 0.01;
|
||||
/// The maximum acceptable amount in satoshi when claiming using zero-conf
|
||||
pub const DEFAULT_ZERO_CONF_MAX_SAT: u64 = 100_000;
|
||||
|
||||
pub(crate) struct ReceiveSwapStateHandler {
|
||||
config: Config,
|
||||
onchain_wallet: Arc<dyn OnchainWallet>,
|
||||
persister: Arc<Persister>,
|
||||
swapper: Arc<dyn Swapper>,
|
||||
@@ -23,12 +30,14 @@ pub(crate) struct ReceiveSwapStateHandler {
|
||||
|
||||
impl ReceiveSwapStateHandler {
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
onchain_wallet: Arc<dyn OnchainWallet>,
|
||||
persister: Arc<Persister>,
|
||||
swapper: Arc<dyn Swapper>,
|
||||
) -> Self {
|
||||
let (subscription_notifier, _) = broadcast::channel::<String>(30);
|
||||
Self {
|
||||
config,
|
||||
onchain_wallet,
|
||||
persister,
|
||||
swapper,
|
||||
@@ -63,16 +72,77 @@ impl ReceiveSwapStateHandler {
|
||||
self.update_swap_info(id, Failed, None, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
// The lockup tx is in the mempool and we accept 0-conf => try to claim
|
||||
// Execute 0-conf preconditions check
|
||||
Ok(RevSwapStates::TransactionMempool) => {
|
||||
let boltzv2::Update::TransactionMempool { transaction, .. } = update else {
|
||||
return Err(anyhow!("Unexpected payload from Boltz status stream"));
|
||||
};
|
||||
|
||||
let lockup_tx_id = &transaction.id;
|
||||
self.update_swap_info(id, Pending, None, Some(lockup_tx_id))
|
||||
.await?;
|
||||
|
||||
if let Some(claim_tx_id) = receive_swap.claim_tx_id {
|
||||
return Err(anyhow!(
|
||||
"Claim tx for Receive Swap {id} was already broadcast: txid {claim_tx_id}"
|
||||
));
|
||||
}
|
||||
|
||||
let lockup_tx = utils::deserialize_tx_hex(&transaction.hex)?;
|
||||
|
||||
// If the amount is greater than the zero-conf limit
|
||||
let max_amount_sat = self.config.zero_conf_max_amount_sat();
|
||||
let receiver_amount_sat = receive_swap.receiver_amount_sat;
|
||||
if receiver_amount_sat > max_amount_sat {
|
||||
warn!("[Receive Swap {id}] Amount is too high to claim with zero-conf ({receiver_amount_sat} sat > {max_amount_sat} sat). Waiting for confirmation...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("[Receive Swap {id}] Amount is within valid range for zero-conf ({receiver_amount_sat} < {max_amount_sat} sat)");
|
||||
|
||||
// If the transaction has RBF, see https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki
|
||||
// TODO: Check for inherent RBF by ensuring all tx ancestors are confirmed
|
||||
let rbf_explicit = lockup_tx.input.iter().any(|input| input.sequence.is_rbf());
|
||||
// let rbf_inherent = lockup_tx_history.height < 0;
|
||||
|
||||
if rbf_explicit {
|
||||
warn!("[Receive Swap {id}] Lockup transaction signals RBF. Waiting for confirmation...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("[Receive Swap {id}] Lockup tx does not signal RBF. Proceeding...");
|
||||
|
||||
// If the fees are higher than our estimated value
|
||||
let tx_fees: u64 = lockup_tx.all_fees().values().sum();
|
||||
let min_fee_rate = self.config.zero_conf_min_fee_rate;
|
||||
let lower_bound_estimated_fees = lockup_tx.vsize() as f32 * min_fee_rate * 0.8;
|
||||
|
||||
if lower_bound_estimated_fees > tx_fees as f32 {
|
||||
warn!("[Receive Swap {id}] Lockup tx fees are too low: Expected at least {lower_bound_estimated_fees} sat, got {tx_fees} sat. Waiting for confirmation...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("[Receive Swap {id}] Lockup tx fees are within acceptable range ({tx_fees} > {lower_bound_estimated_fees} sat). Proceeding with claim.");
|
||||
|
||||
match self.claim(&receive_swap).await {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
PaymentError::AlreadyClaimed => {
|
||||
warn!("Funds already claimed for Receive Swap {id}")
|
||||
}
|
||||
_ => error!("Claim for Receive Swap {id} failed: {err}"),
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(RevSwapStates::TransactionConfirmed) => {
|
||||
// TODO: We need to ensure that the lockup tx is actually confirmed
|
||||
// if lockup_tx_history.height <= 0 {
|
||||
// return Err(anyhow!("Tx state mismatch: Lockup transaction was marked as confirmed by the swapper, but isn't."));
|
||||
// }
|
||||
|
||||
match receive_swap.claim_tx_id {
|
||||
Some(claim_tx_id) => {
|
||||
warn!("Claim tx for Receive Swap {id} was already broadcast: txid {claim_tx_id}")
|
||||
|
||||
@@ -97,6 +97,7 @@ impl LiquidSdk {
|
||||
);
|
||||
|
||||
let receive_swap_state_handler = ReceiveSwapStateHandler::new(
|
||||
config.clone(),
|
||||
onchain_wallet.clone(),
|
||||
persister.clone(),
|
||||
swapper.clone(),
|
||||
|
||||
@@ -2,8 +2,13 @@ use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::error::PaymentError;
|
||||
use anyhow::Result;
|
||||
use lwk_wollet::elements::LockTime::{self, *};
|
||||
use anyhow::{anyhow, Result};
|
||||
use lwk_wollet::elements::encode::deserialize;
|
||||
use lwk_wollet::elements::hex::FromHex;
|
||||
use lwk_wollet::elements::{
|
||||
LockTime::{self, *},
|
||||
Transaction,
|
||||
};
|
||||
|
||||
pub(crate) fn now() -> u32 {
|
||||
SystemTime::now()
|
||||
@@ -38,3 +43,9 @@ pub(crate) fn is_locktime_expired(current_locktime: LockTime, expiry_locktime: L
|
||||
_ => false, // Not using the same units
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_tx_hex(tx_hex: &str) -> Result<Transaction> {
|
||||
Ok(deserialize(&Vec::<u8>::from_hex(tx_hex).map_err(
|
||||
|err| anyhow!("Could not deserialize transaction: {err:?}"),
|
||||
)?)?)
|
||||
}
|
||||
|
||||
@@ -677,13 +677,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
Config dco_decode_config(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 != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
|
||||
return Config(
|
||||
boltzUrl: dco_decode_String(arr[0]),
|
||||
electrumUrl: dco_decode_String(arr[1]),
|
||||
workingDir: dco_decode_String(arr[2]),
|
||||
network: dco_decode_network(arr[3]),
|
||||
paymentTimeoutSec: dco_decode_u_64(arr[4]),
|
||||
zeroConfMinFeeRate: dco_decode_f_32(arr[5]),
|
||||
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[6]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -698,6 +700,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as double;
|
||||
}
|
||||
|
||||
@protected
|
||||
GetInfoResponse dco_decode_get_info_response(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -1203,12 +1211,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
var var_workingDir = sse_decode_String(deserializer);
|
||||
var var_network = sse_decode_network(deserializer);
|
||||
var var_paymentTimeoutSec = sse_decode_u_64(deserializer);
|
||||
var var_zeroConfMinFeeRate = sse_decode_f_32(deserializer);
|
||||
var var_zeroConfMaxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
|
||||
return Config(
|
||||
boltzUrl: var_boltzUrl,
|
||||
electrumUrl: var_electrumUrl,
|
||||
workingDir: var_workingDir,
|
||||
network: var_network,
|
||||
paymentTimeoutSec: var_paymentTimeoutSec);
|
||||
paymentTimeoutSec: var_paymentTimeoutSec,
|
||||
zeroConfMinFeeRate: var_zeroConfMinFeeRate,
|
||||
zeroConfMaxAmountSat: var_zeroConfMaxAmountSat);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -1219,6 +1231,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return ConnectRequest(mnemonic: var_mnemonic, config: var_config);
|
||||
}
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getFloat32();
|
||||
}
|
||||
|
||||
@protected
|
||||
GetInfoResponse sse_decode_get_info_response(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -1631,6 +1649,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return (raw as BindingLiquidSdkImpl).frbInternalCstEncode();
|
||||
}
|
||||
|
||||
@protected
|
||||
double cst_encode_f_32(double raw) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
return raw;
|
||||
}
|
||||
|
||||
@protected
|
||||
int cst_encode_i_32(int raw) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
@@ -1812,6 +1836,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_String(self.workingDir, serializer);
|
||||
sse_encode_network(self.network, serializer);
|
||||
sse_encode_u_64(self.paymentTimeoutSec, serializer);
|
||||
sse_encode_f_32(self.zeroConfMinFeeRate, serializer);
|
||||
sse_encode_opt_box_autoadd_u_64(self.zeroConfMaxAmountSat, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -1821,6 +1847,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_config(self.config, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putFloat32(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_get_info_response(GetInfoResponse self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
@@ -94,6 +94,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ConnectRequest dco_decode_connect_request(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
GetInfoResponse dco_decode_get_info_response(dynamic raw);
|
||||
|
||||
@@ -258,6 +261,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ConnectRequest sse_decode_connect_request(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
GetInfoResponse sse_decode_get_info_response(SseDeserializer deserializer);
|
||||
|
||||
@@ -612,6 +618,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
wireObj.working_dir = cst_encode_String(apiObj.workingDir);
|
||||
wireObj.network = cst_encode_network(apiObj.network);
|
||||
wireObj.payment_timeout_sec = cst_encode_u_64(apiObj.paymentTimeoutSec);
|
||||
wireObj.zero_conf_min_fee_rate = cst_encode_f_32(apiObj.zeroConfMinFeeRate);
|
||||
wireObj.zero_conf_max_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.zeroConfMaxAmountSat);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -890,6 +898,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
int cst_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
|
||||
BindingLiquidSdk raw);
|
||||
|
||||
@protected
|
||||
double cst_encode_f_32(double raw);
|
||||
|
||||
@protected
|
||||
int cst_encode_i_32(int raw);
|
||||
|
||||
@@ -981,6 +992,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_connect_request(ConnectRequest self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_get_info_response(GetInfoResponse self, SseSerializer serializer);
|
||||
|
||||
@@ -1622,12 +1636,9 @@ class RustLibWire implements BaseWire {
|
||||
_dummy_method_to_enforce_bundlingPtr.asFunction<int Function()>();
|
||||
}
|
||||
|
||||
typedef DartPostCObjectFnType = ffi.Pointer<ffi.NativeFunction<DartPostCObjectFnTypeFunction>>;
|
||||
typedef DartPostCObjectFnTypeFunction = ffi.Bool Function(DartPort port_id, ffi.Pointer<ffi.Void> message);
|
||||
typedef DartDartPostCObjectFnTypeFunction = bool Function(
|
||||
DartDartPort port_id, ffi.Pointer<ffi.Void> message);
|
||||
typedef DartPostCObjectFnType
|
||||
= ffi.Pointer<ffi.NativeFunction<ffi.Bool Function(DartPort port_id, ffi.Pointer<ffi.Void> message)>>;
|
||||
typedef DartPort = ffi.Int64;
|
||||
typedef DartDartPort = int;
|
||||
|
||||
final class wire_cst_list_prim_u_8_strict extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Uint8> ptr;
|
||||
@@ -1756,6 +1767,11 @@ final class wire_cst_config extends ffi.Struct {
|
||||
|
||||
@ffi.Uint64()
|
||||
external int payment_timeout_sec;
|
||||
|
||||
@ffi.Float()
|
||||
external double zero_conf_min_fee_rate;
|
||||
|
||||
external ffi.Pointer<ffi.Uint64> zero_conf_max_amount_sat;
|
||||
}
|
||||
|
||||
final class wire_cst_connect_request extends ffi.Struct {
|
||||
@@ -1935,3 +1951,9 @@ final class wire_cst_receive_payment_response extends ffi.Struct {
|
||||
final class wire_cst_send_payment_response extends ffi.Struct {
|
||||
external wire_cst_payment payment;
|
||||
}
|
||||
|
||||
const double DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET = 0.1;
|
||||
|
||||
const double DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET = 0.01;
|
||||
|
||||
const int DEFAULT_ZERO_CONF_MAX_SAT = 100000;
|
||||
|
||||
@@ -42,12 +42,21 @@ class Config {
|
||||
/// Send payment timeout. See [crate::sdk::LiquidSdk::send_payment]
|
||||
final BigInt paymentTimeoutSec;
|
||||
|
||||
/// Zero-conf minimum accepted fee-rate in sat/vbyte
|
||||
final double zeroConfMinFeeRate;
|
||||
|
||||
/// Maximum amount in satoshi to accept zero-conf payments with
|
||||
/// Defaults to [crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT]
|
||||
final BigInt? zeroConfMaxAmountSat;
|
||||
|
||||
const Config({
|
||||
required this.boltzUrl,
|
||||
required this.electrumUrl,
|
||||
required this.workingDir,
|
||||
required this.network,
|
||||
required this.paymentTimeoutSec,
|
||||
required this.zeroConfMinFeeRate,
|
||||
this.zeroConfMaxAmountSat,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -56,7 +65,9 @@ class Config {
|
||||
electrumUrl.hashCode ^
|
||||
workingDir.hashCode ^
|
||||
network.hashCode ^
|
||||
paymentTimeoutSec.hashCode;
|
||||
paymentTimeoutSec.hashCode ^
|
||||
zeroConfMinFeeRate.hashCode ^
|
||||
zeroConfMaxAmountSat.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -67,7 +78,9 @@ class Config {
|
||||
electrumUrl == other.electrumUrl &&
|
||||
workingDir == other.workingDir &&
|
||||
network == other.network &&
|
||||
paymentTimeoutSec == other.paymentTimeoutSec;
|
||||
paymentTimeoutSec == other.paymentTimeoutSec &&
|
||||
zeroConfMinFeeRate == other.zeroConfMinFeeRate &&
|
||||
zeroConfMaxAmountSat == other.zeroConfMaxAmountSat;
|
||||
}
|
||||
|
||||
class ConnectRequest {
|
||||
|
||||
@@ -716,6 +716,11 @@ final class wire_cst_config extends ffi.Struct {
|
||||
|
||||
@ffi.Uint64()
|
||||
external int payment_timeout_sec;
|
||||
|
||||
@ffi.Float()
|
||||
external double zero_conf_min_fee_rate;
|
||||
|
||||
external ffi.Pointer<ffi.Uint64> zero_conf_max_amount_sat;
|
||||
}
|
||||
|
||||
final class wire_cst_connect_request extends ffi.Struct {
|
||||
@@ -898,3 +903,9 @@ final class wire_cst_send_payment_response extends ffi.Struct {
|
||||
|
||||
/// EXTRA BEGIN
|
||||
typedef WireSyncRust2DartDco = ffi.Pointer<DartCObject>;
|
||||
|
||||
const double DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET = 0.1;
|
||||
|
||||
const double DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET = 0.01;
|
||||
|
||||
const int DEFAULT_ZERO_CONF_MAX_SAT = 100000;
|
||||
|
||||
@@ -42,6 +42,7 @@ fun asConfig(config: ReadableMap): Config? {
|
||||
"workingDir",
|
||||
"network",
|
||||
"paymentTimeoutSec",
|
||||
"zeroConfMinFeeRate",
|
||||
),
|
||||
)
|
||||
) {
|
||||
@@ -52,12 +53,25 @@ fun asConfig(config: ReadableMap): Config? {
|
||||
val workingDir = config.getString("workingDir")!!
|
||||
val network = config.getString("network")?.let { asNetwork(it) }!!
|
||||
val paymentTimeoutSec = config.getDouble("paymentTimeoutSec").toULong()
|
||||
val zeroConfMinFeeRate = config.getDouble("zeroConfMinFeeRate")
|
||||
val zeroConfMaxAmountSat =
|
||||
if (hasNonNullKey(
|
||||
config,
|
||||
"zeroConfMaxAmountSat",
|
||||
)
|
||||
) {
|
||||
config.getDouble("zeroConfMaxAmountSat").toULong()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return Config(
|
||||
boltzUrl,
|
||||
electrumUrl,
|
||||
workingDir,
|
||||
network,
|
||||
paymentTimeoutSec,
|
||||
zeroConfMinFeeRate,
|
||||
zeroConfMaxAmountSat,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,6 +82,8 @@ fun readableMapOf(config: Config): ReadableMap =
|
||||
"workingDir" to config.workingDir,
|
||||
"network" to config.network.name.lowercase(),
|
||||
"paymentTimeoutSec" to config.paymentTimeoutSec,
|
||||
"zeroConfMinFeeRate" to config.zeroConfMinFeeRate,
|
||||
"zeroConfMaxAmountSat" to config.zeroConfMaxAmountSat,
|
||||
)
|
||||
|
||||
fun asConfigList(arr: ReadableArray): List<Config> {
|
||||
|
||||
@@ -56,13 +56,25 @@ enum BreezLiquidSDKMapper {
|
||||
guard let paymentTimeoutSec = config["paymentTimeoutSec"] as? UInt64 else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentTimeoutSec", typeName: "Config"))
|
||||
}
|
||||
guard let zeroConfMinFeeRate = config["zeroConfMinFeeRate"] as? Float else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "zeroConfMinFeeRate", typeName: "Config"))
|
||||
}
|
||||
var zeroConfMaxAmountSat: UInt64?
|
||||
if hasNonNilKey(data: config, key: "zeroConfMaxAmountSat") {
|
||||
guard let zeroConfMaxAmountSatTmp = config["zeroConfMaxAmountSat"] as? UInt64 else {
|
||||
throw LiquidSdkError.Generic(message: errUnexpectedValue(fieldName: "zeroConfMaxAmountSat"))
|
||||
}
|
||||
zeroConfMaxAmountSat = zeroConfMaxAmountSatTmp
|
||||
}
|
||||
|
||||
return Config(
|
||||
boltzUrl: boltzUrl,
|
||||
electrumUrl: electrumUrl,
|
||||
workingDir: workingDir,
|
||||
network: network,
|
||||
paymentTimeoutSec: paymentTimeoutSec
|
||||
paymentTimeoutSec: paymentTimeoutSec,
|
||||
zeroConfMinFeeRate: zeroConfMinFeeRate,
|
||||
zeroConfMaxAmountSat: zeroConfMaxAmountSat
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,6 +85,8 @@ enum BreezLiquidSDKMapper {
|
||||
"workingDir": config.workingDir,
|
||||
"network": valueOf(network: config.network),
|
||||
"paymentTimeoutSec": config.paymentTimeoutSec,
|
||||
"zeroConfMinFeeRate": config.zeroConfMinFeeRate,
|
||||
"zeroConfMaxAmountSat": config.zeroConfMaxAmountSat == nil ? nil : config.zeroConfMaxAmountSat,
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface Config {
|
||||
workingDir: string
|
||||
network: Network
|
||||
paymentTimeoutSec: number
|
||||
zeroConfMinFeeRate: number
|
||||
zeroConfMaxAmountSat?: number
|
||||
}
|
||||
|
||||
export interface ConnectRequest {
|
||||
|
||||
Reference in New Issue
Block a user