Fix cooperative refund and improve keypair generation (#223)

* feat: add random keypair generation

* Encapsulate decode_keypair in SendSwap::get_refund_keypair()

* Add refund_tx_id and refund_tx_amount_sat to Payment

* fix: remove blocking on refund

* fix: change `refund_private_key` order

* fix: rebasing

* fix: set `next_unused_address` as refund output

* Handle refunds in `get_info`, `list_payments` (#226)

* Exclude refund txs from payment list

* Adjust balance calculation to account for refunds

* fix: revert boltz changes and fix locktime

* Replace subquery with LEFT JOIN to get refund data

* Rewrite locktime check for more clarity

* Rewrite locktime check for more clarity

* Fix select_payment_query in case of refunds

* Include boltz-client fixes (handling of unwraps for failed broadcasts)

* Cargo.toml: Use boltz-client branch instead of commit

---------

Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com>
This commit is contained in:
yse
2024-05-28 10:07:29 +02:00
committed by GitHub
parent 2fcefee45e
commit 9d2f6f0839
21 changed files with 303 additions and 122 deletions

2
cli/Cargo.lock generated
View File

@@ -370,7 +370,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/ok300/boltz-rust?branch=ok300-breez-latest-05-21#a6254147a0d00756880f5de38ac6000e49f61560" source = "git+https://github.com/ok300/boltz-rust?branch=ok300-breez-latest-05-27#faecead854e8b0803f744b25bd0c47853cc487da"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.31.2", "bitcoin 0.31.2",

2
lib/Cargo.lock generated
View File

@@ -490,7 +490,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/ok300/boltz-rust?branch=ok300-breez-latest-05-21#a6254147a0d00756880f5de38ac6000e49f61560" source = "git+https://github.com/ok300/boltz-rust?branch=ok300-breez-latest-05-27#faecead854e8b0803f744b25bd0c47853cc487da"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.31.2", "bitcoin 0.31.2",

View File

@@ -68,6 +68,8 @@ typedef struct wire_cst_payment {
uint64_t amount_sat; uint64_t amount_sat;
uint64_t *fees_sat; uint64_t *fees_sat;
struct wire_cst_list_prim_u_8_strict *preimage; struct wire_cst_list_prim_u_8_strict *preimage;
struct wire_cst_list_prim_u_8_strict *refund_tx_id;
uint64_t *refund_tx_amount_sat;
int32_t payment_type; int32_t payment_type;
int32_t status; int32_t status;
} wire_cst_payment; } wire_cst_payment;

View File

@@ -84,6 +84,8 @@ dictionary Payment {
u64 amount_sat; u64 amount_sat;
u64? fees_sat = null; u64? fees_sat = null;
string? preimage = null; string? preimage = null;
string? refund_tx_id = null;
u64? refund_tx_amount_sat = null;
PaymentType payment_type; PaymentType payment_type;
PaymentState status; PaymentState status;
}; };

View File

@@ -15,7 +15,7 @@ frb = ["dep:flutter_rust_bridge"]
anyhow = { workspace = true } anyhow = { workspace = true }
bip39 = { version = "2.0.0", features = ["serde"] } bip39 = { version = "2.0.0", features = ["serde"] }
#boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "a05731cc33030ada9ae14afcafe0cded22842ba6" } #boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "a05731cc33030ada9ae14afcafe0cded22842ba6" }
boltz-client = { git = "https://github.com/ok300/boltz-rust", branch = "ok300-breez-latest-05-21" } boltz-client = { git = "https://github.com/ok300/boltz-rust", branch = "ok300-breez-latest-05-27" }
flutter_rust_bridge = { version = "=2.0.0-dev.35", features = ["chrono"], optional = true } flutter_rust_bridge = { version = "=2.0.0-dev.35", features = ["chrono"], optional = true }
log = "0.4.20" log = "0.4.20"
lwk_common = "0.5.1" lwk_common = "0.5.1"

View File

@@ -254,6 +254,8 @@ impl CstDecode<crate::model::Payment> for wire_cst_payment {
amount_sat: self.amount_sat.cst_decode(), amount_sat: self.amount_sat.cst_decode(),
fees_sat: self.fees_sat.cst_decode(), fees_sat: self.fees_sat.cst_decode(),
preimage: self.preimage.cst_decode(), preimage: self.preimage.cst_decode(),
refund_tx_id: self.refund_tx_id.cst_decode(),
refund_tx_amount_sat: self.refund_tx_amount_sat.cst_decode(),
payment_type: self.payment_type.cst_decode(), payment_type: self.payment_type.cst_decode(),
status: self.status.cst_decode(), status: self.status.cst_decode(),
} }
@@ -453,6 +455,8 @@ impl NewWithNullPtr for wire_cst_payment {
amount_sat: Default::default(), amount_sat: Default::default(),
fees_sat: core::ptr::null_mut(), fees_sat: core::ptr::null_mut(),
preimage: core::ptr::null_mut(), preimage: core::ptr::null_mut(),
refund_tx_id: core::ptr::null_mut(),
refund_tx_amount_sat: core::ptr::null_mut(),
payment_type: Default::default(), payment_type: Default::default(),
status: Default::default(), status: Default::default(),
} }
@@ -893,6 +897,8 @@ pub struct wire_cst_payment {
amount_sat: u64, amount_sat: u64,
fees_sat: *mut u64, fees_sat: *mut u64,
preimage: *mut wire_cst_list_prim_u_8_strict, preimage: *mut wire_cst_list_prim_u_8_strict,
refund_tx_id: *mut wire_cst_list_prim_u_8_strict,
refund_tx_amount_sat: *mut u64,
payment_type: i32, payment_type: i32,
status: i32, status: i32,
} }

View File

@@ -778,6 +778,8 @@ impl SseDecode for crate::model::Payment {
let mut var_amountSat = <u64>::sse_decode(deserializer); let mut var_amountSat = <u64>::sse_decode(deserializer);
let mut var_feesSat = <Option<u64>>::sse_decode(deserializer); let mut var_feesSat = <Option<u64>>::sse_decode(deserializer);
let mut var_preimage = <Option<String>>::sse_decode(deserializer); let mut var_preimage = <Option<String>>::sse_decode(deserializer);
let mut var_refundTxId = <Option<String>>::sse_decode(deserializer);
let mut var_refundTxAmountSat = <Option<u64>>::sse_decode(deserializer);
let mut var_paymentType = <crate::model::PaymentType>::sse_decode(deserializer); let mut var_paymentType = <crate::model::PaymentType>::sse_decode(deserializer);
let mut var_status = <crate::model::PaymentState>::sse_decode(deserializer); let mut var_status = <crate::model::PaymentState>::sse_decode(deserializer);
return crate::model::Payment { return crate::model::Payment {
@@ -787,6 +789,8 @@ impl SseDecode for crate::model::Payment {
amount_sat: var_amountSat, amount_sat: var_amountSat,
fees_sat: var_feesSat, fees_sat: var_feesSat,
preimage: var_preimage, preimage: var_preimage,
refund_tx_id: var_refundTxId,
refund_tx_amount_sat: var_refundTxAmountSat,
payment_type: var_paymentType, payment_type: var_paymentType,
status: var_status, status: var_status,
}; };
@@ -1172,6 +1176,8 @@ impl flutter_rust_bridge::IntoDart for crate::model::Payment {
self.amount_sat.into_into_dart().into_dart(), self.amount_sat.into_into_dart().into_dart(),
self.fees_sat.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(),
self.preimage.into_into_dart().into_dart(), self.preimage.into_into_dart().into_dart(),
self.refund_tx_id.into_into_dart().into_dart(),
self.refund_tx_amount_sat.into_into_dart().into_dart(),
self.payment_type.into_into_dart().into_dart(), self.payment_type.into_into_dart().into_dart(),
self.status.into_into_dart().into_dart(), self.status.into_into_dart().into_dart(),
] ]
@@ -1577,6 +1583,8 @@ impl SseEncode for crate::model::Payment {
<u64>::sse_encode(self.amount_sat, serializer); <u64>::sse_encode(self.amount_sat, serializer);
<Option<u64>>::sse_encode(self.fees_sat, serializer); <Option<u64>>::sse_encode(self.fees_sat, serializer);
<Option<String>>::sse_encode(self.preimage, serializer); <Option<String>>::sse_encode(self.preimage, serializer);
<Option<String>>::sse_encode(self.refund_tx_id, serializer);
<Option<u64>>::sse_encode(self.refund_tx_amount_sat, serializer);
<crate::model::PaymentType>::sse_encode(self.payment_type, serializer); <crate::model::PaymentType>::sse_encode(self.payment_type, serializer);
<crate::model::PaymentState>::sse_encode(self.status, serializer); <crate::model::PaymentState>::sse_encode(self.status, serializer);
} }

View File

@@ -3,7 +3,7 @@ use boltz_client::network::Chain;
use boltz_client::swaps::boltzv2::{ use boltz_client::swaps::boltzv2::{
CreateReverseResponse, CreateSubmarineResponse, Leaf, SwapTree, CreateReverseResponse, CreateSubmarineResponse, Leaf, SwapTree,
}; };
use boltz_client::SwapType; use boltz_client::{Keypair, SwapType};
use lwk_signer::SwSigner; use lwk_signer::SwSigner;
use lwk_wollet::{ElectrumUrl, ElementsNetwork, WolletDescriptor}; use lwk_wollet::{ElectrumUrl, ElementsNetwork, WolletDescriptor};
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
@@ -201,8 +201,13 @@ pub(crate) struct SendSwap {
pub(crate) refund_tx_id: Option<String>, pub(crate) refund_tx_id: Option<String>,
pub(crate) created_at: u32, pub(crate) created_at: u32,
pub(crate) state: PaymentState, pub(crate) state: PaymentState,
pub(crate) refund_private_key: String,
} }
impl SendSwap { impl SendSwap {
pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, PaymentError> {
utils::decode_keypair(&self.refund_private_key).map_err(Into::into)
}
pub(crate) fn get_boltz_create_response( pub(crate) fn get_boltz_create_response(
&self, &self,
) -> Result<CreateSubmarineResponse, PaymentError> { ) -> Result<CreateSubmarineResponse, PaymentError> {
@@ -460,6 +465,9 @@ pub struct PaymentSwapData {
/// Amount received by the swap receiver /// Amount received by the swap receiver
pub receiver_amount_sat: u64, pub receiver_amount_sat: u64,
pub refund_tx_id: Option<String>,
pub refund_tx_amount_sat: Option<u64>,
/// Payment status derived from the swap status /// Payment status derived from the swap status
pub status: PaymentState, pub status: PaymentState,
} }
@@ -495,6 +503,12 @@ pub struct Payment {
/// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment).
pub preimage: Option<String>, pub preimage: Option<String>,
/// For a Send swap which was refunded, this is the refund tx id
pub refund_tx_id: Option<String>,
/// For a Send swap which was refunded, this is the refund amount
pub refund_tx_amount_sat: Option<u64>,
pub payment_type: PaymentType, pub payment_type: PaymentType,
/// Composite status representing the overall status of the payment. /// Composite status representing the overall status of the payment.
@@ -518,6 +532,8 @@ impl Payment {
.as_ref() .as_ref()
.map(|s| s.payer_amount_sat - s.receiver_amount_sat), .map(|s| s.payer_amount_sat - s.receiver_amount_sat),
preimage: swap.as_ref().and_then(|s| s.preimage.clone()), preimage: swap.as_ref().and_then(|s| s.preimage.clone()),
refund_tx_id: swap.as_ref().and_then(|s| s.refund_tx_id.clone()),
refund_tx_amount_sat: swap.as_ref().and_then(|s| s.refund_tx_amount_sat),
payment_type: tx.payment_type, payment_type: tx.payment_type,
status: match swap { status: match swap {
Some(swap) => swap.status, Some(swap) => swap.status,

View File

@@ -19,6 +19,7 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
payer_amount_sat INTEGER NOT NULL, payer_amount_sat INTEGER NOT NULL,
receiver_amount_sat INTEGER NOT NULL, receiver_amount_sat INTEGER NOT NULL,
create_response_json TEXT NOT NULL, create_response_json TEXT NOT NULL,
refund_private_key TEXT NOT NULL,
lockup_tx_id TEXT, lockup_tx_id TEXT,
refund_tx_id TEXT, refund_tx_id TEXT,
created_at INTEGER NOT NULL, created_at INTEGER NOT NULL,

View File

@@ -109,15 +109,21 @@ impl Persister {
ss.id, ss.id,
ss.created_at, ss.created_at,
ss.preimage, ss.preimage,
ss.refund_tx_id,
ss.payer_amount_sat, ss.payer_amount_sat,
ss.receiver_amount_sat, ss.receiver_amount_sat,
ss.state ss.state,
FROM payment_tx_data AS ptx rtx.amount_sat
LEFT JOIN receive_swaps AS rs FROM payment_tx_data AS ptx -- Payment tx (each tx results in a Payment)
LEFT JOIN receive_swaps AS rs -- Receive Swap data
ON ptx.tx_id = rs.claim_tx_id ON ptx.tx_id = rs.claim_tx_id
LEFT JOIN send_swaps AS ss LEFT JOIN send_swaps AS ss -- Send Swap data
ON ptx.tx_id = ss.lockup_tx_id ON ptx.tx_id = ss.lockup_tx_id
WHERE {} LEFT JOIN payment_tx_data AS rtx -- Refund tx data
ON rtx.tx_id = ss.refund_tx_id
WHERE -- Filter out refund txs from Payment tx list
ptx.tx_id NOT IN (SELECT refund_tx_id FROM send_swaps WHERE refund_tx_id NOT NULL)
AND {}
", ",
where_clause.unwrap_or("true") where_clause.unwrap_or("true")
) )
@@ -137,12 +143,15 @@ impl Persister {
let maybe_receive_swap_payer_amount_sat: Option<u64> = row.get(7)?; let maybe_receive_swap_payer_amount_sat: Option<u64> = row.get(7)?;
let maybe_receive_swap_receiver_amount_sat: Option<u64> = row.get(8)?; let maybe_receive_swap_receiver_amount_sat: Option<u64> = row.get(8)?;
let maybe_receive_swap_receiver_state: Option<PaymentState> = row.get(9)?; let maybe_receive_swap_receiver_state: Option<PaymentState> = row.get(9)?;
let maybe_send_swap_id: Option<String> = row.get(10)?; let maybe_send_swap_id: Option<String> = row.get(10)?;
let maybe_send_swap_created_at: Option<u32> = row.get(11)?; let maybe_send_swap_created_at: Option<u32> = row.get(11)?;
let maybe_send_swap_preimage: Option<String> = row.get(12)?; let maybe_send_swap_preimage: Option<String> = row.get(12)?;
let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(13)?; let maybe_send_swap_refund_tx_id: Option<String> = row.get(13)?;
let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(14)?; let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(14)?;
let maybe_send_swap_state: Option<PaymentState> = row.get(15)?; let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(15)?;
let maybe_send_swap_state: Option<PaymentState> = row.get(16)?;
let maybe_send_swap_refund_tx_amount_sat: Option<u64> = row.get(17)?;
let swap = match maybe_receive_swap_id { let swap = match maybe_receive_swap_id {
Some(receive_swap_id) => Some(PaymentSwapData { Some(receive_swap_id) => Some(PaymentSwapData {
@@ -151,6 +160,8 @@ impl Persister {
preimage: None, preimage: None,
payer_amount_sat: maybe_receive_swap_payer_amount_sat.unwrap_or(0), payer_amount_sat: maybe_receive_swap_payer_amount_sat.unwrap_or(0),
receiver_amount_sat: maybe_receive_swap_receiver_amount_sat.unwrap_or(0), receiver_amount_sat: maybe_receive_swap_receiver_amount_sat.unwrap_or(0),
refund_tx_id: None,
refund_tx_amount_sat: None,
status: maybe_receive_swap_receiver_state.unwrap_or(PaymentState::Created), status: maybe_receive_swap_receiver_state.unwrap_or(PaymentState::Created),
}), }),
None => maybe_send_swap_id.map(|send_swap_id| PaymentSwapData { None => maybe_send_swap_id.map(|send_swap_id| PaymentSwapData {
@@ -159,6 +170,8 @@ impl Persister {
preimage: maybe_send_swap_preimage, preimage: maybe_send_swap_preimage,
payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0), payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0),
receiver_amount_sat: maybe_send_swap_receiver_amount_sat.unwrap_or(0), receiver_amount_sat: maybe_send_swap_receiver_amount_sat.unwrap_or(0),
refund_tx_id: maybe_send_swap_refund_tx_id,
refund_tx_amount_sat: maybe_send_swap_refund_tx_amount_sat,
status: maybe_send_swap_state.unwrap_or(PaymentState::Created), status: maybe_send_swap_state.unwrap_or(PaymentState::Created),
}), }),
}; };

View File

@@ -11,7 +11,7 @@ use rusqlite::{named_params, params, Connection, Row};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
impl Persister { impl Persister {
pub(crate) fn insert_receive_swap(&self, receive_swap: ReceiveSwap) -> Result<()> { pub(crate) fn insert_receive_swap(&self, receive_swap: &ReceiveSwap) -> Result<()> {
let con = self.get_connection()?; let con = self.get_connection()?;
let mut stmt = con.prepare( let mut stmt = con.prepare(
@@ -31,16 +31,16 @@ impl Persister {
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)?; )?;
_ = stmt.execute(( _ = stmt.execute((
receive_swap.id, &receive_swap.id,
receive_swap.preimage, &receive_swap.preimage,
receive_swap.create_response_json, &receive_swap.create_response_json,
receive_swap.invoice, &receive_swap.invoice,
receive_swap.payer_amount_sat, &receive_swap.payer_amount_sat,
receive_swap.receiver_amount_sat, &receive_swap.receiver_amount_sat,
receive_swap.created_at, &receive_swap.created_at,
receive_swap.claim_fees_sat, &receive_swap.claim_fees_sat,
receive_swap.claim_tx_id, &receive_swap.claim_tx_id,
receive_swap.state, &receive_swap.state,
))?; ))?;
Ok(()) Ok(())

View File

@@ -11,7 +11,7 @@ use crate::model::*;
use crate::persist::Persister; use crate::persist::Persister;
impl Persister { impl Persister {
pub(crate) fn insert_send_swap(&self, send_swap: SendSwap) -> Result<()> { pub(crate) fn insert_send_swap(&self, send_swap: &SendSwap) -> Result<()> {
let con = self.get_connection()?; let con = self.get_connection()?;
let mut stmt = con.prepare( let mut stmt = con.prepare(
@@ -22,23 +22,25 @@ impl Persister {
payer_amount_sat, payer_amount_sat,
receiver_amount_sat, receiver_amount_sat,
create_response_json, create_response_json,
refund_private_key,
lockup_tx_id, lockup_tx_id,
refund_tx_id, refund_tx_id,
created_at, created_at,
state state
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)?; )?;
_ = stmt.execute(( _ = stmt.execute((
send_swap.id, &send_swap.id,
send_swap.invoice, &send_swap.invoice,
send_swap.payer_amount_sat, &send_swap.payer_amount_sat,
send_swap.receiver_amount_sat, &send_swap.receiver_amount_sat,
send_swap.create_response_json, &send_swap.create_response_json,
send_swap.lockup_tx_id, &send_swap.refund_private_key,
send_swap.refund_tx_id, &send_swap.lockup_tx_id,
send_swap.created_at, &send_swap.refund_tx_id,
send_swap.state, &send_swap.created_at,
&send_swap.state,
))?; ))?;
Ok(()) Ok(())
@@ -59,6 +61,7 @@ impl Persister {
payer_amount_sat, payer_amount_sat,
receiver_amount_sat, receiver_amount_sat,
create_response_json, create_response_json,
refund_private_key,
lockup_tx_id, lockup_tx_id,
refund_tx_id, refund_tx_id,
created_at, created_at,
@@ -85,10 +88,11 @@ impl Persister {
payer_amount_sat: row.get(2)?, payer_amount_sat: row.get(2)?,
receiver_amount_sat: row.get(3)?, receiver_amount_sat: row.get(3)?,
create_response_json: row.get(4)?, create_response_json: row.get(4)?,
lockup_tx_id: row.get(5)?, refund_private_key: row.get(5)?,
refund_tx_id: row.get(6)?, lockup_tx_id: row.get(6)?,
created_at: row.get(7)?, refund_tx_id: row.get(7)?,
state: row.get(8)?, created_at: row.get(8)?,
state: row.get(9)?,
}) })
} }

View File

@@ -1,4 +1,6 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use boltz_client::network::Chain;
use boltz_client::ToHex;
use boltz_client::{ use boltz_client::{
network::electrum::ElectrumConfig, network::electrum::ElectrumConfig,
swaps::{ swaps::{
@@ -13,7 +15,7 @@ use log::{debug, error, info, warn};
use lwk_common::{singlesig_desc, Signer, Singlesig}; use lwk_common::{singlesig_desc, Signer, Singlesig};
use lwk_signer::{AnySigner, SwSigner}; use lwk_signer::{AnySigner, SwSigner};
use lwk_wollet::bitcoin::Witness; use lwk_wollet::bitcoin::Witness;
use lwk_wollet::elements::hex::ToHex; use lwk_wollet::elements::{LockTime, LockTime::*};
use lwk_wollet::hashes::{sha256, Hash}; use lwk_wollet::hashes::{sha256, Hash};
use lwk_wollet::{ use lwk_wollet::{
elements::{Address, Transaction}, elements::{Address, Transaction},
@@ -148,23 +150,6 @@ impl LiquidSdk {
Ok(descriptor_str.parse()?) Ok(descriptor_str.parse()?)
} }
fn get_submarine_keys(&self, derivation_index: i32) -> Result<Keypair, PaymentError> {
let mnemonic = self
.lwk_signer
.mnemonic()
.ok_or(PaymentError::SignerError {
err: "Could not claim: Mnemonic not found".to_string(),
})?;
let swap_key = SwapKey::from_submarine_account(
&mnemonic.to_string(),
"",
self.network.into(),
derivation_index as u64,
)?;
let lsk = LiquidSwapKey::try_from(swap_key)?;
Ok(lsk.keypair)
}
fn validate_state_transition( fn validate_state_transition(
from_state: PaymentState, from_state: PaymentState,
to_state: PaymentState, to_state: PaymentState,
@@ -406,20 +391,12 @@ impl LiquidSdk {
.persister .persister
.fetch_send_swap(id)? .fetch_send_swap(id)?
.ok_or(anyhow!("No ongoing Send Swap found for ID {id}"))?; .ok_or(anyhow!("No ongoing Send Swap found for ID {id}"))?;
let create_response: CreateSubmarineResponse =
ongoing_send_swap.get_boltz_create_response()?;
let receiver_amount_sat = get_invoice_amount!(ongoing_send_swap.invoice);
let keypair = self.get_submarine_keys(0)?;
match swap_state { match swap_state {
SubSwapStates::TransactionClaimPending => { SubSwapStates::TransactionClaimPending => {
let lockup_tx_id = ongoing_send_swap.lockup_tx_id.ok_or(anyhow!( let keypair = ongoing_send_swap.get_refund_keypair()?;
"Swap-in {id} is pending but no lockup txid is present"
))?;
let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp(
&create_response, &ongoing_send_swap.get_boltz_create_response()?,
keypair.public_key().into(), keypair.public_key().into(),
) )
.map_err(|e| anyhow!("Could not rebuild refund details for swap-in {id}: {e:?}"))?; .map_err(|e| anyhow!("Could not rebuild refund details for swap-in {id}: {e:?}"))?;
@@ -445,21 +422,13 @@ impl LiquidSdk {
Ok(()) Ok(())
} }
// If swap state is unrecoverable, try refunding
SubSwapStates::TransactionLockupFailed SubSwapStates::TransactionLockupFailed
| SubSwapStates::InvoiceFailedToPay | SubSwapStates::InvoiceFailedToPay
| SubSwapStates::SwapExpired => { | SubSwapStates::SwapExpired => {
warn!("Swap-in {id} is in an unrecoverable state: {swap_state:?}"); warn!("Swap-in {id} is in an unrecoverable state: {swap_state:?}");
// If swap state is unrecoverable, try refunding let refund_tx_id = self.try_refund(&ongoing_send_swap).await?;
let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp(
&create_response,
keypair.public_key().into(),
)
.map_err(|e| anyhow!("Could not rebuild refund details for swap-in {id}: {e:?}"))?;
let refund_tx_id = self
.try_refund(id, &swap_script, &keypair, receiver_amount_sat)
.await?;
info!("Broadcast refund tx for Swap-in {id}. Tx id: {refund_tx_id}"); info!("Broadcast refund tx for Swap-in {id}. Tx id: {refund_tx_id}");
self.try_handle_send_swap_update(id, Pending, None, None, Some(&refund_tx_id)) self.try_handle_send_swap_update(id, Pending, None, None, Some(&refund_tx_id))
.await?; .await?;
@@ -495,13 +464,23 @@ impl LiquidSdk {
for p in self.list_payments()? { for p in self.list_payments()? {
match p.payment_type { match p.payment_type {
PaymentType::Send => match p.status { PaymentType::Send => match p.status {
PaymentState::Complete => confirmed_sent_sat += p.amount_sat, Complete => confirmed_sent_sat += p.amount_sat,
PaymentState::Failed => {} Failed => {
_ => pending_send_sat += p.amount_sat, confirmed_sent_sat += p.amount_sat;
confirmed_received_sat += p.refund_tx_amount_sat.unwrap_or_default();
}
Pending => match p.refund_tx_amount_sat {
Some(refund_tx_amount_sat) => {
confirmed_sent_sat += p.amount_sat;
pending_receive_sat += refund_tx_amount_sat;
}
None => pending_send_sat += p.amount_sat,
},
Created => pending_send_sat += p.amount_sat,
}, },
PaymentType::Receive => match p.status { PaymentType::Receive => match p.status {
PaymentState::Complete => confirmed_received_sat += p.amount_sat, Complete => confirmed_received_sat += p.amount_sat,
PaymentState::Failed => {} Failed => {}
_ => pending_receive_sat += p.amount_sat, _ => pending_receive_sat += p.amount_sat,
}, },
} }
@@ -652,50 +631,118 @@ impl LiquidSdk {
async fn new_refund_tx( async fn new_refund_tx(
&self, &self,
swap_id: &str,
swap_script: &LBtcSwapScriptV2, swap_script: &LBtcSwapScriptV2,
) -> Result<LBtcSwapTxV2, PaymentError> { ) -> Result<LBtcSwapTxV2, PaymentError> {
let wallet = self.lwk_wollet.lock().await; let output_address = self.next_unused_address().await?.to_string();
let output_address = wallet.address(Some(0))?.address().to_string();
let network_config = self.network_config(); let network_config = self.network_config();
Ok(LBtcSwapTxV2::new_refund( Ok(LBtcSwapTxV2::new_refund(
swap_script.clone(), swap_script.clone(),
&output_address, &output_address,
&network_config, &network_config,
self.boltz_url_v2().to_string(),
swap_id.to_string(),
)?) )?)
} }
async fn try_refund( async fn try_refund_cooperative(
&self, &self,
swap_id: &str, swap: &SendSwap,
swap_script: &LBtcSwapScriptV2, refund_tx: &LBtcSwapTxV2,
keypair: &Keypair, broadcast_fees_sat: Amount,
amount_sat: u64, is_lowball: Option<(&BoltzApiClientV2, Chain)>,
) -> Result<String, PaymentError> { ) -> Result<String, PaymentError> {
let refund_tx = self.new_refund_tx(swap_script).await?; info!("Initiating cooperative refund for Send Swap {}", &swap.id);
let tx = refund_tx.sign_refund(
&swap.get_refund_keypair()?,
broadcast_fees_sat,
Some((&self.boltz_client_v2(), &swap.id)),
)?;
let refund_tx_id = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?;
info!(
"Successfully broadcast cooperative refund for Send Swap {}",
&swap.id
);
Ok(refund_tx_id.clone())
}
async fn try_refund_non_cooperative(
&self,
swap: &SendSwap,
swap_script: &LBtcSwapScriptV2,
refund_tx: LBtcSwapTxV2,
broadcast_fees_sat: Amount,
is_lowball: Option<(&BoltzApiClientV2, Chain)>,
) -> Result<String, PaymentError> {
info!(
"Initiating non-cooperative refund for Send Swap {}",
&swap.id
);
let current_height = self.lwk_wollet.lock().await.tip().height();
let locktime_from_height =
LockTime::from_height(current_height).map_err(|e| PaymentError::Generic {
err: format!("Cannot convert current block height to lock time: {e:?}"),
})?;
info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime);
let is_locktime_satisfied = match (locktime_from_height, swap_script.locktime) {
(Blocks(n), Blocks(lock_time)) => n >= lock_time,
(Seconds(n), Seconds(lock_time)) => n >= lock_time,
_ => false, // Not using the same units
};
if !is_locktime_satisfied {
return Err(PaymentError::Generic {
err: format!(
"Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}",
locktime_from_height, swap_script.locktime
)
});
}
let tx = refund_tx.sign_refund(&swap.get_refund_keypair()?, broadcast_fees_sat, None)?;
let refund_tx_id = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?;
info!(
"Successfully broadcast non-cooperative refund for swap-in {}",
swap.id
);
Ok(refund_tx_id)
}
async fn try_refund(&self, swap: &SendSwap) -> Result<String, PaymentError> {
let id = &swap.id;
let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp(
&swap.get_boltz_create_response()?,
swap.get_refund_keypair()?.public_key().into(),
)
.map_err(|e| anyhow!("Could not rebuild refund details for swap-in {id}: {e:?}"))?;
let refund_tx = self.new_refund_tx(&swap.id, &swap_script).await?;
let amount_sat = get_invoice_amount!(swap.invoice);
let broadcast_fees_sat = let broadcast_fees_sat =
Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat).await?); Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat).await?);
let client = self.boltz_client_v2(); let client = self.boltz_client_v2();
let is_lowball = Some((&client, boltz_client::network::Chain::from(self.network))); let is_lowball = match self.network {
Network::Liquid => None,
Network::LiquidTestnet => Some((&client, boltz_client::network::Chain::LiquidTestnet)),
};
match refund_tx.sign_refund( match self
keypair, .try_refund_cooperative(swap, &refund_tx, broadcast_fees_sat, is_lowball)
broadcast_fees_sat, .await
Some((&client, &swap_id.to_string())), {
) { Ok(res) => Ok(res),
// Try with cooperative refund
Ok(tx) => {
let refund_tx_id = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?;
debug!("Successfully broadcast cooperative refund for swap-in {swap_id}");
Ok(refund_tx_id)
}
// Try with non-cooperative refund
Err(e) => { Err(e) => {
debug!("Cooperative refund failed: {:?}", e); warn!("Cooperative refund failed: {:?}", e);
let tx = refund_tx.sign_refund(keypair, broadcast_fees_sat, None)?; self.try_refund_non_cooperative(
let refund_tx_id = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?; swap,
debug!("Successfully broadcast non-cooperative refund for swap-in {swap_id}"); &swap_script,
Ok(refund_tx_id) refund_tx,
broadcast_fees_sat,
is_lowball,
)
.await
} }
} }
} }
@@ -723,7 +770,7 @@ impl LiquidSdk {
) -> Result<(), PaymentError> { ) -> Result<(), PaymentError> {
debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim");
let client = self.boltz_client_v2(); let client = self.boltz_client_v2();
let refund_tx = self.new_refund_tx(swap_script).await?; let refund_tx = self.new_refund_tx(swap_id, swap_script).await?;
let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?; let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?;
debug!("Received claim tx details: {:?}", &claim_tx_response); debug!("Received claim tx details: {:?}", &claim_tx_response);
@@ -783,7 +830,7 @@ impl LiquidSdk {
PaymentError::InvalidOrExpiredFees PaymentError::InvalidOrExpiredFees
); );
let keypair = self.get_submarine_keys(0)?; let keypair = utils::generate_keypair();
let refund_public_key = boltz_client::PublicKey { let refund_public_key = boltz_client::PublicKey {
compressed: true, compressed: true,
inner: keypair.public_key(), inner: keypair.public_key(),
@@ -820,7 +867,7 @@ impl LiquidSdk {
BoltzStatusStream::mark_swap_as_tracked(swap_id, SwapType::Submarine); BoltzStatusStream::mark_swap_as_tracked(swap_id, SwapType::Submarine);
let payer_amount_sat = req.fees_sat + receiver_amount_sat; let payer_amount_sat = req.fees_sat + receiver_amount_sat;
self.persister.insert_send_swap(SendSwap { let swap = SendSwap {
id: swap_id.clone(), id: swap_id.clone(),
invoice: req.invoice.clone(), invoice: req.invoice.clone(),
payer_amount_sat, payer_amount_sat,
@@ -830,7 +877,9 @@ impl LiquidSdk {
refund_tx_id: None, refund_tx_id: None,
created_at: utils::now(), created_at: utils::now(),
state: PaymentState::Created, state: PaymentState::Created,
})?; refund_private_key: keypair.display_secret().to_string(),
};
self.persister.insert_send_swap(&swap)?;
let result; let result;
let mut lockup_tx_id = String::new(); let mut lockup_tx_id = String::new();
@@ -913,8 +962,15 @@ impl LiquidSdk {
SubSwapStates::InvoiceFailedToPay SubSwapStates::InvoiceFailedToPay
| SubSwapStates::SwapExpired | SubSwapStates::SwapExpired
| SubSwapStates::TransactionLockupFailed => { | SubSwapStates::TransactionLockupFailed => {
let refund_tx_id = self let refund_tx_id = self.try_refund(&swap).await?;
.try_refund(swap_id, &swap_script, &keypair, receiver_amount_sat)
self.try_handle_send_swap_update(
swap_id,
Pending,
None,
None,
Some(&refund_tx_id),
)
.await?; .await?;
result = Err(PaymentError::Refunded { result = Err(PaymentError::Refunded {
@@ -1080,7 +1136,7 @@ impl LiquidSdk {
&invoice.to_string(), &invoice.to_string(),
)?; )?;
self.persister self.persister
.insert_receive_swap(ReceiveSwap { .insert_receive_swap(&ReceiveSwap {
id: swap_id.clone(), id: swap_id.clone(),
preimage: preimage_str, preimage: preimage_str,
create_response_json, create_response_json,
@@ -1153,7 +1209,7 @@ impl LiquidSdk {
info!("Retrieving preimage from non-cooperative claim tx"); info!("Retrieving preimage from non-cooperative claim tx");
let id = &swap.id; let id = &swap.id;
let keypair = self.get_submarine_keys(0)?; let keypair = swap.get_refund_keypair()?;
let create_response = swap.get_boltz_create_response()?; let create_response = swap.get_boltz_create_response()?;
let electrum_client = ElectrumClient::new(&self.electrum_url)?; let electrum_client = ElectrumClient::new(&self.electrum_url)?;

View File

@@ -44,11 +44,13 @@ pub(crate) fn get_swap_status_v2(
return match args.first() { return match args.first() {
Some(update) if update.id == swap_id => { Some(update) if update.id == swap_id => {
info!("Got new swap status: {}", update.status); info!("Got new swap status for {swap_id}: {}", update.status);
Ok(update.status.clone()) Ok(update.status.clone())
} }
Some(update) => Err(anyhow!("WS reply has wrong swap ID {update:?}")), Some(update) => Err(anyhow!(
"WS reply has wrong swap ID {update:?}. Should be {swap_id}"
)),
None => Err(anyhow!("WS reply contains no update")), None => Err(anyhow!("WS reply contains no update")),
}; };
} }
@@ -64,7 +66,9 @@ pub(crate) fn get_swap_status_v2(
for e in &args { for e in &args {
error!("Got error: {} for swap: {}", e.error, e.id); error!("Got error: {} for swap: {}", e.error, e.id);
} }
return Err(anyhow!("Got SwapUpdate errors: {args:?}")); return Err(anyhow!(
"Got SwapUpdate errors for swap {swap_id}: {args:?}"
));
} }
} }
} }
@@ -82,3 +86,16 @@ pub(crate) fn json_to_pubkey(json: &str) -> Result<boltz_client::PublicKey, Paym
err: format!("Failed to deserialize PublicKey: {e:?}"), err: format!("Failed to deserialize PublicKey: {e:?}"),
}) })
} }
pub(crate) fn generate_keypair() -> boltz_client::Keypair {
let secp = boltz_client::Secp256k1::new();
let mut rng = bip39::rand::rngs::OsRng;
let secret_key = lwk_wollet::secp256k1::SecretKey::new(&mut rng);
boltz_client::Keypair::from_secret_key(&secp, &secret_key)
}
pub(crate) fn decode_keypair(secret_key: &str) -> Result<boltz_client::Keypair, lwk_wollet::Error> {
let secp = boltz_client::Secp256k1::new();
let secret_key = lwk_wollet::secp256k1::SecretKey::from_str(secret_key)?;
Ok(boltz_client::Keypair::from_secret_key(&secp, &secret_key))
}

View File

@@ -669,7 +669,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
Payment dco_decode_payment(dynamic raw) { Payment dco_decode_payment(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 != 8) throw Exception('unexpected arr length: expect 8 but see ${arr.length}'); if (arr.length != 10) throw Exception('unexpected arr length: expect 10 but see ${arr.length}');
return Payment( return Payment(
txId: dco_decode_String(arr[0]), txId: dco_decode_String(arr[0]),
swapId: dco_decode_opt_String(arr[1]), swapId: dco_decode_opt_String(arr[1]),
@@ -677,8 +677,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
amountSat: dco_decode_u_64(arr[3]), amountSat: dco_decode_u_64(arr[3]),
feesSat: dco_decode_opt_box_autoadd_u_64(arr[4]), feesSat: dco_decode_opt_box_autoadd_u_64(arr[4]),
preimage: dco_decode_opt_String(arr[5]), preimage: dco_decode_opt_String(arr[5]),
paymentType: dco_decode_payment_type(arr[6]), refundTxId: dco_decode_opt_String(arr[6]),
status: dco_decode_payment_state(arr[7]), refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[7]),
paymentType: dco_decode_payment_type(arr[8]),
status: dco_decode_payment_state(arr[9]),
); );
} }
@@ -1090,6 +1092,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_amountSat = sse_decode_u_64(deserializer); var var_amountSat = sse_decode_u_64(deserializer);
var var_feesSat = sse_decode_opt_box_autoadd_u_64(deserializer); var var_feesSat = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_preimage = sse_decode_opt_String(deserializer); var var_preimage = sse_decode_opt_String(deserializer);
var var_refundTxId = sse_decode_opt_String(deserializer);
var var_refundTxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_paymentType = sse_decode_payment_type(deserializer); var var_paymentType = sse_decode_payment_type(deserializer);
var var_status = sse_decode_payment_state(deserializer); var var_status = sse_decode_payment_state(deserializer);
return Payment( return Payment(
@@ -1099,6 +1103,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
amountSat: var_amountSat, amountSat: var_amountSat,
feesSat: var_feesSat, feesSat: var_feesSat,
preimage: var_preimage, preimage: var_preimage,
refundTxId: var_refundTxId,
refundTxAmountSat: var_refundTxAmountSat,
paymentType: var_paymentType, paymentType: var_paymentType,
status: var_status); status: var_status);
} }
@@ -1546,6 +1552,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_u_64(self.amountSat, serializer); sse_encode_u_64(self.amountSat, serializer);
sse_encode_opt_box_autoadd_u_64(self.feesSat, serializer); sse_encode_opt_box_autoadd_u_64(self.feesSat, serializer);
sse_encode_opt_String(self.preimage, serializer); sse_encode_opt_String(self.preimage, serializer);
sse_encode_opt_String(self.refundTxId, serializer);
sse_encode_opt_box_autoadd_u_64(self.refundTxAmountSat, serializer);
sse_encode_payment_type(self.paymentType, serializer); sse_encode_payment_type(self.paymentType, serializer);
sse_encode_payment_state(self.status, serializer); sse_encode_payment_state(self.status, serializer);
} }

View File

@@ -569,6 +569,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat); wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat);
wireObj.fees_sat = cst_encode_opt_box_autoadd_u_64(apiObj.feesSat); wireObj.fees_sat = cst_encode_opt_box_autoadd_u_64(apiObj.feesSat);
wireObj.preimage = cst_encode_opt_String(apiObj.preimage); wireObj.preimage = cst_encode_opt_String(apiObj.preimage);
wireObj.refund_tx_id = cst_encode_opt_String(apiObj.refundTxId);
wireObj.refund_tx_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.refundTxAmountSat);
wireObj.payment_type = cst_encode_payment_type(apiObj.paymentType); wireObj.payment_type = cst_encode_payment_type(apiObj.paymentType);
wireObj.status = cst_encode_payment_state(apiObj.status); wireObj.status = cst_encode_payment_state(apiObj.status);
} }
@@ -1369,6 +1371,10 @@ final class wire_cst_payment extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> preimage; external ffi.Pointer<wire_cst_list_prim_u_8_strict> preimage;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> refund_tx_id;
external ffi.Pointer<ffi.Uint64> refund_tx_amount_sat;
@ffi.Int32() @ffi.Int32()
external int payment_type; external int payment_type;

View File

@@ -161,6 +161,12 @@ class Payment {
/// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment).
final String? preimage; final String? preimage;
/// For a Send swap which was refunded, this is the refund tx id
final String? refundTxId;
/// For a Send swap which was refunded, this is the refund amount
final int? refundTxAmountSat;
final PaymentType paymentType; final PaymentType paymentType;
/// Composite status representing the overall status of the payment. /// Composite status representing the overall status of the payment.
@@ -177,6 +183,8 @@ class Payment {
required this.amountSat, required this.amountSat,
this.feesSat, this.feesSat,
this.preimage, this.preimage,
this.refundTxId,
this.refundTxAmountSat,
required this.paymentType, required this.paymentType,
required this.status, required this.status,
}); });
@@ -189,6 +197,8 @@ class Payment {
amountSat.hashCode ^ amountSat.hashCode ^
feesSat.hashCode ^ feesSat.hashCode ^
preimage.hashCode ^ preimage.hashCode ^
refundTxId.hashCode ^
refundTxAmountSat.hashCode ^
paymentType.hashCode ^ paymentType.hashCode ^
status.hashCode; status.hashCode;
@@ -203,6 +213,8 @@ class Payment {
amountSat == other.amountSat && amountSat == other.amountSat &&
feesSat == other.feesSat && feesSat == other.feesSat &&
preimage == other.preimage && preimage == other.preimage &&
refundTxId == other.refundTxId &&
refundTxAmountSat == other.refundTxAmountSat &&
paymentType == other.paymentType && paymentType == other.paymentType &&
status == other.status; status == other.status;
} }

View File

@@ -539,6 +539,10 @@ final class wire_cst_payment extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> preimage; external ffi.Pointer<wire_cst_list_prim_u_8_strict> preimage;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> refund_tx_id;
external ffi.Pointer<ffi.Uint64> refund_tx_amount_sat;
@ffi.Int32() @ffi.Int32()
external int payment_type; external int payment_type;

View File

@@ -172,6 +172,8 @@ fun asPayment(payment: ReadableMap): Payment? {
val amountSat = payment.getDouble("amountSat").toULong() val amountSat = payment.getDouble("amountSat").toULong()
val feesSat = if (hasNonNullKey(payment, "feesSat")) payment.getDouble("feesSat").toULong() else null val feesSat = if (hasNonNullKey(payment, "feesSat")) payment.getDouble("feesSat").toULong() else null
val preimage = if (hasNonNullKey(payment, "preimage")) payment.getString("preimage") else null val preimage = if (hasNonNullKey(payment, "preimage")) payment.getString("preimage") else null
val refundTxId = if (hasNonNullKey(payment, "refundTxId")) payment.getString("refundTxId") else null
val refundTxAmountSat = if (hasNonNullKey(payment, "refundTxAmountSat")) payment.getDouble("refundTxAmountSat").toULong() else null
val paymentType = payment.getString("paymentType")?.let { asPaymentType(it) }!! val paymentType = payment.getString("paymentType")?.let { asPaymentType(it) }!!
val status = payment.getString("status")?.let { asPaymentState(it) }!! val status = payment.getString("status")?.let { asPaymentState(it) }!!
return Payment( return Payment(
@@ -181,6 +183,8 @@ fun asPayment(payment: ReadableMap): Payment? {
amountSat, amountSat,
feesSat, feesSat,
preimage, preimage,
refundTxId,
refundTxAmountSat,
paymentType, paymentType,
status, status,
) )
@@ -194,6 +198,8 @@ fun readableMapOf(payment: Payment): ReadableMap {
"amountSat" to payment.amountSat, "amountSat" to payment.amountSat,
"feesSat" to payment.feesSat, "feesSat" to payment.feesSat,
"preimage" to payment.preimage, "preimage" to payment.preimage,
"refundTxId" to payment.refundTxId,
"refundTxAmountSat" to payment.refundTxAmountSat,
"paymentType" to payment.paymentType.name.lowercase(), "paymentType" to payment.paymentType.name.lowercase(),
"status" to payment.status.name.lowercase(), "status" to payment.status.name.lowercase(),
) )

View File

@@ -198,6 +198,20 @@ enum BreezLiquidSDKMapper {
} }
preimage = preimageTmp preimage = preimageTmp
} }
var refundTxId: String?
if hasNonNilKey(data: payment, key: "refundTxId") {
guard let refundTxIdTmp = payment["refundTxId"] as? String else {
throw LiquidSdkError.Generic(message: errUnexpectedValue(fieldName: "refundTxId"))
}
refundTxId = refundTxIdTmp
}
var refundTxAmountSat: UInt64?
if hasNonNilKey(data: payment, key: "refundTxAmountSat") {
guard let refundTxAmountSatTmp = payment["refundTxAmountSat"] as? UInt64 else {
throw LiquidSdkError.Generic(message: errUnexpectedValue(fieldName: "refundTxAmountSat"))
}
refundTxAmountSat = refundTxAmountSatTmp
}
guard let paymentTypeTmp = payment["paymentType"] as? String else { guard let paymentTypeTmp = payment["paymentType"] as? String else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentType", typeName: "Payment")) throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentType", typeName: "Payment"))
} }
@@ -215,6 +229,8 @@ enum BreezLiquidSDKMapper {
amountSat: amountSat, amountSat: amountSat,
feesSat: feesSat, feesSat: feesSat,
preimage: preimage, preimage: preimage,
refundTxId: refundTxId,
refundTxAmountSat: refundTxAmountSat,
paymentType: paymentType, paymentType: paymentType,
status: status status: status
) )
@@ -228,6 +244,8 @@ enum BreezLiquidSDKMapper {
"amountSat": payment.amountSat, "amountSat": payment.amountSat,
"feesSat": payment.feesSat == nil ? nil : payment.feesSat, "feesSat": payment.feesSat == nil ? nil : payment.feesSat,
"preimage": payment.preimage == nil ? nil : payment.preimage, "preimage": payment.preimage == nil ? nil : payment.preimage,
"refundTxId": payment.refundTxId == nil ? nil : payment.refundTxId,
"refundTxAmountSat": payment.refundTxAmountSat == nil ? nil : payment.refundTxAmountSat,
"paymentType": valueOf(paymentType: payment.paymentType), "paymentType": valueOf(paymentType: payment.paymentType),
"status": valueOf(paymentState: payment.status), "status": valueOf(paymentState: payment.status),
] ]

View File

@@ -47,6 +47,8 @@ export interface Payment {
amountSat: number amountSat: number
feesSat?: number feesSat?: number
preimage?: string preimage?: string
refundTxId?: string
refundTxAmountSat?: number
paymentType: PaymentType paymentType: PaymentType
status: PaymentState status: PaymentState
} }