Chain swap refund tx fee bumping (#688)

* Chain swap refund tx fee bumping

* Add "pending" to refund tx id field names

* Rename pending to last
This commit is contained in:
Daniel Granhão
2025-01-24 09:41:32 +00:00
committed by GitHub
parent 6ec6c96709
commit 53fb22ab41
18 changed files with 636 additions and 503 deletions

2
cli/Cargo.lock generated
View File

@@ -707,7 +707,7 @@ dependencies = [
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/SatoshiPortal/boltz-rust?rev=540b5cb6505a97f1d02d846bf544bd5c1f800b25#540b5cb6505a97f1d02d846bf544bd5c1f800b25" source = "git+https://github.com/danielgranhao/boltz-rust?rev=e3e87186604c818c667b1293149423af5757dfbe#e3e87186604c818c667b1293149423af5757dfbe"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.32.5", "bitcoin 0.32.5",

926
lib/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -711,6 +711,7 @@ typedef struct wire_cst_refundable_swap {
struct wire_cst_list_prim_u_8_strict *swap_address; struct wire_cst_list_prim_u_8_strict *swap_address;
uint32_t timestamp; uint32_t timestamp;
uint64_t amount_sat; uint64_t amount_sat;
struct wire_cst_list_prim_u_8_strict *last_refund_tx_id;
} wire_cst_refundable_swap; } wire_cst_refundable_swap;
typedef struct wire_cst_list_refundable_swap { typedef struct wire_cst_list_refundable_swap {
@@ -1064,7 +1065,7 @@ typedef struct wire_cst_payment_error {
typedef struct wire_cst_prepare_refund_response { typedef struct wire_cst_prepare_refund_response {
uint32_t tx_vsize; uint32_t tx_vsize;
uint64_t tx_fee_sat; uint64_t tx_fee_sat;
struct wire_cst_list_prim_u_8_strict *refund_tx_id; struct wire_cst_list_prim_u_8_strict *last_refund_tx_id;
} wire_cst_prepare_refund_response; } wire_cst_prepare_refund_response;
typedef struct wire_cst_receive_payment_response { typedef struct wire_cst_receive_payment_response {

View File

@@ -618,6 +618,7 @@ dictionary RefundableSwap {
string swap_address; string swap_address;
u32 timestamp; u32 timestamp;
u64 amount_sat; u64 amount_sat;
string? last_refund_tx_id;
}; };
dictionary RecommendedFees { dictionary RecommendedFees {
@@ -637,7 +638,7 @@ dictionary PrepareRefundRequest {
dictionary PrepareRefundResponse { dictionary PrepareRefundResponse {
u32 tx_vsize; u32 tx_vsize;
u64 tx_fee_sat; u64 tx_fee_sat;
string? refund_tx_id = null; string? last_refund_tx_id = null;
}; };
dictionary RefundRequest { dictionary RefundRequest {

View File

@@ -17,7 +17,7 @@ workspace = true
[dependencies] [dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
bip39 = "2.0.0" bip39 = "2.0.0"
boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "540b5cb6505a97f1d02d846bf544bd5c1f800b25" } boltz-client = { git = "https://github.com/danielgranhao/boltz-rust", rev = "e3e87186604c818c667b1293149423af5757dfbe" }
chrono = "0.4" chrono = "0.4"
derivative = "2.2.0" derivative = "2.2.0"
env_logger = "0.11" env_logger = "0.11"

View File

@@ -2,14 +2,13 @@ use std::time::Duration;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use boltz_client::Amount;
use electrum_client::{ use electrum_client::{
bitcoin::{ bitcoin::{
consensus::{deserialize, serialize}, consensus::{deserialize, serialize},
hashes::{sha256, Hash}, hashes::{sha256, Hash},
Address, OutPoint, Script, Transaction, TxOut, Txid, Address, OutPoint, Script, Transaction, Txid,
}, },
Client, ElectrumApi, GetBalanceRes, HeaderNotification, ListUnspentRes, Client, ElectrumApi, GetBalanceRes, HeaderNotification,
}; };
use log::info; use log::info;
use lwk_wollet::{ElectrumOptions, ElectrumUrl, Error, History}; use lwk_wollet::{ElectrumOptions, ElectrumUrl, Error, History};
@@ -188,27 +187,46 @@ impl BitcoinChainService for HybridBitcoinChainService {
} }
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> { async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
let utxos = self // Get confirmed transactions involving our script
.client let history: Vec<_> = self
.script_list_unspent(script)? .get_script_history(script)?
.iter() .into_iter()
.map( .filter(|h| h.height > 0)
|ListUnspentRes {
tx_hash,
tx_pos,
value,
..
}| {
Utxo::Bitcoin((
OutPoint::new(*tx_hash, *tx_pos as u32),
TxOut {
value: Amount::from_sat(*value),
script_pubkey: script.into(),
},
))
},
)
.collect(); .collect();
let txs = self.get_transactions(
&history
.iter()
.map(|h| h.txid.to_raw_hash().into())
.collect::<Vec<_>>(),
)?;
// Find all unspent outputs paying to our script
let utxos = txs
.iter()
.flat_map(|tx| {
tx.output
.iter()
.enumerate()
.filter(|(_, output)| output.script_pubkey == *script)
.filter(|(vout, _)| {
// Check if output is unspent
!txs.iter().any(|spending_tx| {
// Check if any input spends our output
spending_tx.input.iter().any(|input| {
input.previous_output.txid == tx.compute_txid()
&& input.previous_output.vout == *vout as u32
})
})
})
.map(|(vout, output)| {
Utxo::Bitcoin((
OutPoint::new(tx.compute_txid(), vout as u32),
output.clone(),
))
})
})
.collect();
Ok(utxos) Ok(utxos)
} }

View File

@@ -1167,7 +1167,7 @@ impl ChainSwapHandler {
err: format!("Cannot transition from {from_state:?} to Refundable state"), err: format!("Cannot transition from {from_state:?} to Refundable state"),
}), }),
(Pending | WaitingFeeAcceptance | Refundable, RefundPending) => Ok(()), (Pending | WaitingFeeAcceptance | Refundable | RefundPending, RefundPending) => Ok(()),
(_, RefundPending) => Err(PaymentError::Generic { (_, RefundPending) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to RefundPending state"), err: format!("Cannot transition from {from_state:?} to RefundPending state"),
}), }),
@@ -1530,7 +1530,10 @@ mod tests {
(TimedOut, HashSet::from([Failed])), (TimedOut, HashSet::from([Failed])),
(Complete, HashSet::from([Refundable])), (Complete, HashSet::from([Refundable])),
(Refundable, HashSet::from([RefundPending, Failed])), (Refundable, HashSet::from([RefundPending, Failed])),
(RefundPending, HashSet::from([Refundable, Complete, Failed])), (
RefundPending,
HashSet::from([Refundable, Complete, Failed, RefundPending]),
),
(Failed, HashSet::from([Failed, Refundable])), (Failed, HashSet::from([Failed, Refundable])),
]); ]);

View File

@@ -4093,11 +4093,11 @@ impl SseDecode for crate::model::PrepareRefundResponse {
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_txVsize = <u32>::sse_decode(deserializer); let mut var_txVsize = <u32>::sse_decode(deserializer);
let mut var_txFeeSat = <u64>::sse_decode(deserializer); let mut var_txFeeSat = <u64>::sse_decode(deserializer);
let mut var_refundTxId = <Option<String>>::sse_decode(deserializer); let mut var_lastRefundTxId = <Option<String>>::sse_decode(deserializer);
return crate::model::PrepareRefundResponse { return crate::model::PrepareRefundResponse {
tx_vsize: var_txVsize, tx_vsize: var_txVsize,
tx_fee_sat: var_txFeeSat, tx_fee_sat: var_txFeeSat,
refund_tx_id: var_refundTxId, last_refund_tx_id: var_lastRefundTxId,
}; };
} }
} }
@@ -4211,10 +4211,12 @@ impl SseDecode for crate::model::RefundableSwap {
let mut var_swapAddress = <String>::sse_decode(deserializer); let mut var_swapAddress = <String>::sse_decode(deserializer);
let mut var_timestamp = <u32>::sse_decode(deserializer); let mut var_timestamp = <u32>::sse_decode(deserializer);
let mut var_amountSat = <u64>::sse_decode(deserializer); let mut var_amountSat = <u64>::sse_decode(deserializer);
let mut var_lastRefundTxId = <Option<String>>::sse_decode(deserializer);
return crate::model::RefundableSwap { return crate::model::RefundableSwap {
swap_address: var_swapAddress, swap_address: var_swapAddress,
timestamp: var_timestamp, timestamp: var_timestamp,
amount_sat: var_amountSat, amount_sat: var_amountSat,
last_refund_tx_id: var_lastRefundTxId,
}; };
} }
} }
@@ -6343,7 +6345,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareRefundResponse {
[ [
self.tx_vsize.into_into_dart().into_dart(), self.tx_vsize.into_into_dart().into_dart(),
self.tx_fee_sat.into_into_dart().into_dart(), self.tx_fee_sat.into_into_dart().into_dart(),
self.refund_tx_id.into_into_dart().into_dart(), self.last_refund_tx_id.into_into_dart().into_dart(),
] ]
.into_dart() .into_dart()
} }
@@ -6522,6 +6524,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::RefundableSwap {
self.swap_address.into_into_dart().into_dart(), self.swap_address.into_into_dart().into_dart(),
self.timestamp.into_into_dart().into_dart(), self.timestamp.into_into_dart().into_dart(),
self.amount_sat.into_into_dart().into_dart(), self.amount_sat.into_into_dart().into_dart(),
self.last_refund_tx_id.into_into_dart().into_dart(),
] ]
.into_dart() .into_dart()
} }
@@ -8358,7 +8361,7 @@ impl SseEncode for crate::model::PrepareRefundResponse {
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<u32>::sse_encode(self.tx_vsize, serializer); <u32>::sse_encode(self.tx_vsize, serializer);
<u64>::sse_encode(self.tx_fee_sat, serializer); <u64>::sse_encode(self.tx_fee_sat, serializer);
<Option<String>>::sse_encode(self.refund_tx_id, serializer); <Option<String>>::sse_encode(self.last_refund_tx_id, serializer);
} }
} }
@@ -8435,6 +8438,7 @@ impl SseEncode for crate::model::RefundableSwap {
<String>::sse_encode(self.swap_address, serializer); <String>::sse_encode(self.swap_address, serializer);
<u32>::sse_encode(self.timestamp, serializer); <u32>::sse_encode(self.timestamp, serializer);
<u64>::sse_encode(self.amount_sat, serializer); <u64>::sse_encode(self.amount_sat, serializer);
<Option<String>>::sse_encode(self.last_refund_tx_id, serializer);
} }
} }
@@ -10398,7 +10402,7 @@ mod io {
crate::model::PrepareRefundResponse { crate::model::PrepareRefundResponse {
tx_vsize: self.tx_vsize.cst_decode(), tx_vsize: self.tx_vsize.cst_decode(),
tx_fee_sat: self.tx_fee_sat.cst_decode(), tx_fee_sat: self.tx_fee_sat.cst_decode(),
refund_tx_id: self.refund_tx_id.cst_decode(), last_refund_tx_id: self.last_refund_tx_id.cst_decode(),
} }
} }
} }
@@ -10484,6 +10488,7 @@ mod io {
swap_address: self.swap_address.cst_decode(), swap_address: self.swap_address.cst_decode(),
timestamp: self.timestamp.cst_decode(), timestamp: self.timestamp.cst_decode(),
amount_sat: self.amount_sat.cst_decode(), amount_sat: self.amount_sat.cst_decode(),
last_refund_tx_id: self.last_refund_tx_id.cst_decode(),
} }
} }
} }
@@ -11671,7 +11676,7 @@ mod io {
Self { Self {
tx_vsize: Default::default(), tx_vsize: Default::default(),
tx_fee_sat: Default::default(), tx_fee_sat: Default::default(),
refund_tx_id: core::ptr::null_mut(), last_refund_tx_id: core::ptr::null_mut(),
} }
} }
} }
@@ -11793,6 +11798,7 @@ mod io {
swap_address: core::ptr::null_mut(), swap_address: core::ptr::null_mut(),
timestamp: Default::default(), timestamp: Default::default(),
amount_sat: Default::default(), amount_sat: Default::default(),
last_refund_tx_id: core::ptr::null_mut(),
} }
} }
} }
@@ -13952,7 +13958,7 @@ mod io {
pub struct wire_cst_prepare_refund_response { pub struct wire_cst_prepare_refund_response {
tx_vsize: u32, tx_vsize: u32,
tx_fee_sat: u64, tx_fee_sat: u64,
refund_tx_id: *mut wire_cst_list_prim_u_8_strict, last_refund_tx_id: *mut wire_cst_list_prim_u_8_strict,
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@@ -14011,6 +14017,7 @@ mod io {
swap_address: *mut wire_cst_list_prim_u_8_strict, swap_address: *mut wire_cst_list_prim_u_8_strict,
timestamp: u32, timestamp: u32,
amount_sat: u64, amount_sat: u64,
last_refund_tx_id: *mut wire_cst_list_prim_u_8_strict,
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View File

@@ -513,7 +513,8 @@ pub struct PrepareRefundRequest {
pub struct PrepareRefundResponse { pub struct PrepareRefundResponse {
pub tx_vsize: u32, pub tx_vsize: u32,
pub tx_fee_sat: u64, pub tx_fee_sat: u64,
pub refund_tx_id: Option<String>, /// The txid of the last broadcasted refund tx, if any
pub last_refund_tx_id: Option<String>,
} }
/// An argument when calling [crate::sdk::LiquidSdk::refund]. /// An argument when calling [crate::sdk::LiquidSdk::refund].
@@ -874,6 +875,7 @@ impl ChainSwap {
swap_address: self.lockup_address.clone(), swap_address: self.lockup_address.clone(),
timestamp: self.created_at, timestamp: self.created_at,
amount_sat: refundable_amount_sat, amount_sat: refundable_amount_sat,
last_refund_tx_id: self.refund_tx_id.clone(),
} }
} }
@@ -1119,6 +1121,8 @@ pub struct RefundableSwap {
pub timestamp: u32, pub timestamp: u32,
/// Amount that is refundable, from all UTXOs /// Amount that is refundable, from all UTXOs
pub amount_sat: u64, pub amount_sat: u64,
/// The txid of the last broadcasted refund tx, if any
pub last_refund_tx_id: Option<String>,
} }
/// The payment state of an individual payment. /// The payment state of an individual payment.
@@ -1232,7 +1236,9 @@ impl PaymentState {
pub(crate) fn is_refundable(&self) -> bool { pub(crate) fn is_refundable(&self) -> bool {
matches!( matches!(
self, self,
PaymentState::Refundable | PaymentState::WaitingFeeAcceptance PaymentState::Refundable
| PaymentState::RefundPending
| PaymentState::WaitingFeeAcceptance
) )
} }
} }

View File

@@ -268,7 +268,7 @@ impl Persister {
} }
pub(crate) fn list_refundable_chain_swaps(&self) -> Result<Vec<ChainSwap>> { pub(crate) fn list_refundable_chain_swaps(&self) -> Result<Vec<ChainSwap>> {
self.list_chain_swaps_by_state(vec![PaymentState::Refundable]) self.list_chain_swaps_by_state(vec![PaymentState::Refundable, PaymentState::RefundPending])
} }
pub(crate) fn list_local_chain_swaps(&self) -> Result<Vec<ChainSwap>> { pub(crate) fn list_local_chain_swaps(&self) -> Result<Vec<ChainSwap>> {

View File

@@ -2227,7 +2227,7 @@ impl LiquidSdk {
Ok(PrepareRefundResponse { Ok(PrepareRefundResponse {
tx_vsize, tx_vsize,
tx_fee_sat, tx_fee_sat,
refund_tx_id, last_refund_tx_id: refund_tx_id,
}) })
} }

View File

@@ -2928,7 +2928,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return PrepareRefundResponse( return PrepareRefundResponse(
txVsize: dco_decode_u_32(arr[0]), txVsize: dco_decode_u_32(arr[0]),
txFeeSat: dco_decode_u_64(arr[1]), txFeeSat: dco_decode_u_64(arr[1]),
refundTxId: dco_decode_opt_String(arr[2]), lastRefundTxId: dco_decode_opt_String(arr[2]),
); );
} }
@@ -3027,11 +3027,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
RefundableSwap dco_decode_refundable_swap(dynamic raw) { RefundableSwap dco_decode_refundable_swap(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>; final arr = raw as List<dynamic>;
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); if (arr.length != 4) throw Exception('unexpected arr length: expect 4 but see ${arr.length}');
return RefundableSwap( return RefundableSwap(
swapAddress: dco_decode_String(arr[0]), swapAddress: dco_decode_String(arr[0]),
timestamp: dco_decode_u_32(arr[1]), timestamp: dco_decode_u_32(arr[1]),
amountSat: dco_decode_u_64(arr[2]), amountSat: dco_decode_u_64(arr[2]),
lastRefundTxId: dco_decode_opt_String(arr[3]),
); );
} }
@@ -5109,8 +5110,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
var var_txVsize = sse_decode_u_32(deserializer); var var_txVsize = sse_decode_u_32(deserializer);
var var_txFeeSat = sse_decode_u_64(deserializer); var var_txFeeSat = sse_decode_u_64(deserializer);
var var_refundTxId = sse_decode_opt_String(deserializer); var var_lastRefundTxId = sse_decode_opt_String(deserializer);
return PrepareRefundResponse(txVsize: var_txVsize, txFeeSat: var_txFeeSat, refundTxId: var_refundTxId); return PrepareRefundResponse(
txVsize: var_txVsize, txFeeSat: var_txFeeSat, lastRefundTxId: var_lastRefundTxId);
} }
@protected @protected
@@ -5197,7 +5199,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_swapAddress = sse_decode_String(deserializer); var var_swapAddress = sse_decode_String(deserializer);
var var_timestamp = sse_decode_u_32(deserializer); var var_timestamp = sse_decode_u_32(deserializer);
var var_amountSat = sse_decode_u_64(deserializer); var var_amountSat = sse_decode_u_64(deserializer);
return RefundableSwap(swapAddress: var_swapAddress, timestamp: var_timestamp, amountSat: var_amountSat); var var_lastRefundTxId = sse_decode_opt_String(deserializer);
return RefundableSwap(
swapAddress: var_swapAddress,
timestamp: var_timestamp,
amountSat: var_amountSat,
lastRefundTxId: var_lastRefundTxId);
} }
@protected @protected
@@ -7081,7 +7088,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_u_32(self.txVsize, serializer); sse_encode_u_32(self.txVsize, serializer);
sse_encode_u_64(self.txFeeSat, serializer); sse_encode_u_64(self.txFeeSat, serializer);
sse_encode_opt_String(self.refundTxId, serializer); sse_encode_opt_String(self.lastRefundTxId, serializer);
} }
@protected @protected
@@ -7149,6 +7156,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(self.swapAddress, serializer); sse_encode_String(self.swapAddress, serializer);
sse_encode_u_32(self.timestamp, serializer); sse_encode_u_32(self.timestamp, serializer);
sse_encode_u_64(self.amountSat, serializer); sse_encode_u_64(self.amountSat, serializer);
sse_encode_opt_String(self.lastRefundTxId, serializer);
} }
@protected @protected

View File

@@ -3185,7 +3185,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
PrepareRefundResponse apiObj, wire_cst_prepare_refund_response wireObj) { PrepareRefundResponse apiObj, wire_cst_prepare_refund_response wireObj) {
wireObj.tx_vsize = cst_encode_u_32(apiObj.txVsize); wireObj.tx_vsize = cst_encode_u_32(apiObj.txVsize);
wireObj.tx_fee_sat = cst_encode_u_64(apiObj.txFeeSat); wireObj.tx_fee_sat = cst_encode_u_64(apiObj.txFeeSat);
wireObj.refund_tx_id = cst_encode_opt_String(apiObj.refundTxId); wireObj.last_refund_tx_id = cst_encode_opt_String(apiObj.lastRefundTxId);
} }
@protected @protected
@@ -3248,6 +3248,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.swap_address = cst_encode_String(apiObj.swapAddress); wireObj.swap_address = cst_encode_String(apiObj.swapAddress);
wireObj.timestamp = cst_encode_u_32(apiObj.timestamp); wireObj.timestamp = cst_encode_u_32(apiObj.timestamp);
wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat); wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat);
wireObj.last_refund_tx_id = cst_encode_opt_String(apiObj.lastRefundTxId);
} }
@protected @protected
@@ -6701,6 +6702,8 @@ final class wire_cst_refundable_swap extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int amount_sat; external int amount_sat;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> last_refund_tx_id;
} }
final class wire_cst_list_refundable_swap extends ffi.Struct { final class wire_cst_list_refundable_swap extends ffi.Struct {
@@ -7136,7 +7139,7 @@ final class wire_cst_prepare_refund_response extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int tx_fee_sat; external int tx_fee_sat;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> refund_tx_id; external ffi.Pointer<wire_cst_list_prim_u_8_strict> last_refund_tx_id;
} }
final class wire_cst_receive_payment_response extends ffi.Struct { final class wire_cst_receive_payment_response extends ffi.Struct {

View File

@@ -1231,16 +1231,18 @@ class PrepareRefundRequest {
class PrepareRefundResponse { class PrepareRefundResponse {
final int txVsize; final int txVsize;
final BigInt txFeeSat; final BigInt txFeeSat;
final String? refundTxId;
/// The txid of the last broadcasted refund tx, if any
final String? lastRefundTxId;
const PrepareRefundResponse({ const PrepareRefundResponse({
required this.txVsize, required this.txVsize,
required this.txFeeSat, required this.txFeeSat,
this.refundTxId, this.lastRefundTxId,
}); });
@override @override
int get hashCode => txVsize.hashCode ^ txFeeSat.hashCode ^ refundTxId.hashCode; int get hashCode => txVsize.hashCode ^ txFeeSat.hashCode ^ lastRefundTxId.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -1249,7 +1251,7 @@ class PrepareRefundResponse {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
txVsize == other.txVsize && txVsize == other.txVsize &&
txFeeSat == other.txFeeSat && txFeeSat == other.txFeeSat &&
refundTxId == other.refundTxId; lastRefundTxId == other.lastRefundTxId;
} }
/// An argument when calling [crate::sdk::LiquidSdk::prepare_send_payment]. /// An argument when calling [crate::sdk::LiquidSdk::prepare_send_payment].
@@ -1440,14 +1442,19 @@ class RefundableSwap {
/// Amount that is refundable, from all UTXOs /// Amount that is refundable, from all UTXOs
final BigInt amountSat; final BigInt amountSat;
/// The txid of the last broadcasted refund tx, if any
final String? lastRefundTxId;
const RefundableSwap({ const RefundableSwap({
required this.swapAddress, required this.swapAddress,
required this.timestamp, required this.timestamp,
required this.amountSat, required this.amountSat,
this.lastRefundTxId,
}); });
@override @override
int get hashCode => swapAddress.hashCode ^ timestamp.hashCode ^ amountSat.hashCode; int get hashCode =>
swapAddress.hashCode ^ timestamp.hashCode ^ amountSat.hashCode ^ lastRefundTxId.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -1456,7 +1463,8 @@ class RefundableSwap {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
swapAddress == other.swapAddress && swapAddress == other.swapAddress &&
timestamp == other.timestamp && timestamp == other.timestamp &&
amountSat == other.amountSat; amountSat == other.amountSat &&
lastRefundTxId == other.lastRefundTxId;
} }
/// An argument when calling [crate::sdk::LiquidSdk::restore]. /// An argument when calling [crate::sdk::LiquidSdk::restore].

View File

@@ -5024,6 +5024,8 @@ final class wire_cst_refundable_swap extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int amount_sat; external int amount_sat;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> last_refund_tx_id;
} }
final class wire_cst_list_refundable_swap extends ffi.Struct { final class wire_cst_list_refundable_swap extends ffi.Struct {
@@ -5459,7 +5461,7 @@ final class wire_cst_prepare_refund_response extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int tx_fee_sat; external int tx_fee_sat;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> refund_tx_id; external ffi.Pointer<wire_cst_list_prim_u_8_strict> last_refund_tx_id;
} }
final class wire_cst_receive_payment_response extends ffi.Struct { final class wire_cst_receive_payment_response extends ffi.Struct {

View File

@@ -2083,15 +2083,24 @@ fun asPrepareRefundResponse(prepareRefundResponse: ReadableMap): PrepareRefundRe
} }
val txVsize = prepareRefundResponse.getInt("txVsize").toUInt() val txVsize = prepareRefundResponse.getInt("txVsize").toUInt()
val txFeeSat = prepareRefundResponse.getDouble("txFeeSat").toULong() val txFeeSat = prepareRefundResponse.getDouble("txFeeSat").toULong()
val refundTxId = if (hasNonNullKey(prepareRefundResponse, "refundTxId")) prepareRefundResponse.getString("refundTxId") else null val lastRefundTxId =
return PrepareRefundResponse(txVsize, txFeeSat, refundTxId) if (hasNonNullKey(
prepareRefundResponse,
"lastRefundTxId",
)
) {
prepareRefundResponse.getString("lastRefundTxId")
} else {
null
}
return PrepareRefundResponse(txVsize, txFeeSat, lastRefundTxId)
} }
fun readableMapOf(prepareRefundResponse: PrepareRefundResponse): ReadableMap = fun readableMapOf(prepareRefundResponse: PrepareRefundResponse): ReadableMap =
readableMapOf( readableMapOf(
"txVsize" to prepareRefundResponse.txVsize, "txVsize" to prepareRefundResponse.txVsize,
"txFeeSat" to prepareRefundResponse.txFeeSat, "txFeeSat" to prepareRefundResponse.txFeeSat,
"refundTxId" to prepareRefundResponse.refundTxId, "lastRefundTxId" to prepareRefundResponse.lastRefundTxId,
) )
fun asPrepareRefundResponseList(arr: ReadableArray): List<PrepareRefundResponse> { fun asPrepareRefundResponseList(arr: ReadableArray): List<PrepareRefundResponse> {
@@ -2399,7 +2408,8 @@ fun asRefundableSwap(refundableSwap: ReadableMap): RefundableSwap? {
val swapAddress = refundableSwap.getString("swapAddress")!! val swapAddress = refundableSwap.getString("swapAddress")!!
val timestamp = refundableSwap.getInt("timestamp").toUInt() val timestamp = refundableSwap.getInt("timestamp").toUInt()
val amountSat = refundableSwap.getDouble("amountSat").toULong() val amountSat = refundableSwap.getDouble("amountSat").toULong()
return RefundableSwap(swapAddress, timestamp, amountSat) val lastRefundTxId = if (hasNonNullKey(refundableSwap, "lastRefundTxId")) refundableSwap.getString("lastRefundTxId") else null
return RefundableSwap(swapAddress, timestamp, amountSat, lastRefundTxId)
} }
fun readableMapOf(refundableSwap: RefundableSwap): ReadableMap = fun readableMapOf(refundableSwap: RefundableSwap): ReadableMap =
@@ -2407,6 +2417,7 @@ fun readableMapOf(refundableSwap: RefundableSwap): ReadableMap =
"swapAddress" to refundableSwap.swapAddress, "swapAddress" to refundableSwap.swapAddress,
"timestamp" to refundableSwap.timestamp, "timestamp" to refundableSwap.timestamp,
"amountSat" to refundableSwap.amountSat, "amountSat" to refundableSwap.amountSat,
"lastRefundTxId" to refundableSwap.lastRefundTxId,
) )
fun asRefundableSwapList(arr: ReadableArray): List<RefundableSwap> { fun asRefundableSwapList(arr: ReadableArray): List<RefundableSwap> {

View File

@@ -2399,22 +2399,22 @@ enum BreezSDKLiquidMapper {
guard let txFeeSat = prepareRefundResponse["txFeeSat"] as? UInt64 else { guard let txFeeSat = prepareRefundResponse["txFeeSat"] as? UInt64 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "txFeeSat", typeName: "PrepareRefundResponse")) throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "txFeeSat", typeName: "PrepareRefundResponse"))
} }
var refundTxId: String? var lastRefundTxId: String?
if hasNonNilKey(data: prepareRefundResponse, key: "refundTxId") { if hasNonNilKey(data: prepareRefundResponse, key: "lastRefundTxId") {
guard let refundTxIdTmp = prepareRefundResponse["refundTxId"] as? String else { guard let lastRefundTxIdTmp = prepareRefundResponse["lastRefundTxId"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "refundTxId")) throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lastRefundTxId"))
} }
refundTxId = refundTxIdTmp lastRefundTxId = lastRefundTxIdTmp
} }
return PrepareRefundResponse(txVsize: txVsize, txFeeSat: txFeeSat, refundTxId: refundTxId) return PrepareRefundResponse(txVsize: txVsize, txFeeSat: txFeeSat, lastRefundTxId: lastRefundTxId)
} }
static func dictionaryOf(prepareRefundResponse: PrepareRefundResponse) -> [String: Any?] { static func dictionaryOf(prepareRefundResponse: PrepareRefundResponse) -> [String: Any?] {
return [ return [
"txVsize": prepareRefundResponse.txVsize, "txVsize": prepareRefundResponse.txVsize,
"txFeeSat": prepareRefundResponse.txFeeSat, "txFeeSat": prepareRefundResponse.txFeeSat,
"refundTxId": prepareRefundResponse.refundTxId == nil ? nil : prepareRefundResponse.refundTxId, "lastRefundTxId": prepareRefundResponse.lastRefundTxId == nil ? nil : prepareRefundResponse.lastRefundTxId,
] ]
} }
@@ -2750,8 +2750,15 @@ enum BreezSDKLiquidMapper {
guard let amountSat = refundableSwap["amountSat"] as? UInt64 else { guard let amountSat = refundableSwap["amountSat"] as? UInt64 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amountSat", typeName: "RefundableSwap")) throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amountSat", typeName: "RefundableSwap"))
} }
var lastRefundTxId: String?
if hasNonNilKey(data: refundableSwap, key: "lastRefundTxId") {
guard let lastRefundTxIdTmp = refundableSwap["lastRefundTxId"] as? String else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lastRefundTxId"))
}
lastRefundTxId = lastRefundTxIdTmp
}
return RefundableSwap(swapAddress: swapAddress, timestamp: timestamp, amountSat: amountSat) return RefundableSwap(swapAddress: swapAddress, timestamp: timestamp, amountSat: amountSat, lastRefundTxId: lastRefundTxId)
} }
static func dictionaryOf(refundableSwap: RefundableSwap) -> [String: Any?] { static func dictionaryOf(refundableSwap: RefundableSwap) -> [String: Any?] {
@@ -2759,6 +2766,7 @@ enum BreezSDKLiquidMapper {
"swapAddress": refundableSwap.swapAddress, "swapAddress": refundableSwap.swapAddress,
"timestamp": refundableSwap.timestamp, "timestamp": refundableSwap.timestamp,
"amountSat": refundableSwap.amountSat, "amountSat": refundableSwap.amountSat,
"lastRefundTxId": refundableSwap.lastRefundTxId == nil ? nil : refundableSwap.lastRefundTxId,
] ]
} }

View File

@@ -358,7 +358,7 @@ export interface PrepareRefundRequest {
export interface PrepareRefundResponse { export interface PrepareRefundResponse {
txVsize: number txVsize: number
txFeeSat: number txFeeSat: number
refundTxId?: string lastRefundTxId?: string
} }
export interface PrepareSendRequest { export interface PrepareSendRequest {
@@ -408,6 +408,7 @@ export interface RefundableSwap {
swapAddress: string swapAddress: string
timestamp: number timestamp: number
amountSat: number amountSat: number
lastRefundTxId?: string
} }
export interface RestoreRequest { export interface RestoreRequest {