Persist swapper fees for swaps (#586)

* Add new Payment field swapper_fees_sat

* Add swapper fee handling for Receive Swaps

* Add swapper fee handling for Send Swaps

* Add swapper fee handling for Chain Swaps

* Fix tests

* Send Swap: persist pair JSON instead of service fee

* Receive Swap: persist pair JSON instead of service fee

* Chain Swap: persist pair JSON instead of service fee
This commit is contained in:
ok300
2024-12-06 08:32:03 +00:00
committed by GitHub
parent 3a9b6a6dcd
commit 790dfa91be
19 changed files with 295 additions and 114 deletions

View File

@@ -442,6 +442,7 @@ typedef struct wire_cst_payment {
uint32_t timestamp; uint32_t timestamp;
uint64_t amount_sat; uint64_t amount_sat;
uint64_t fees_sat; uint64_t fees_sat;
uint64_t *swapper_fees_sat;
int32_t payment_type; int32_t payment_type;
int32_t status; int32_t status;
struct wire_cst_payment_details details; struct wire_cst_payment_details details;

View File

@@ -546,6 +546,7 @@ dictionary Payment {
PaymentType payment_type; PaymentType payment_type;
PaymentState status; PaymentState status;
PaymentDetails details; PaymentDetails details;
u64? swapper_fees_sat = null;
string? destination = null; string? destination = null;
string? tx_id = null; string? tx_id = null;
}; };

View File

@@ -3445,6 +3445,7 @@ impl SseDecode for crate::model::Payment {
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_feesSat = <u64>::sse_decode(deserializer); let mut var_feesSat = <u64>::sse_decode(deserializer);
let mut var_swapperFeesSat = <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);
let mut var_details = <crate::model::PaymentDetails>::sse_decode(deserializer); let mut var_details = <crate::model::PaymentDetails>::sse_decode(deserializer);
@@ -3454,6 +3455,7 @@ impl SseDecode for crate::model::Payment {
timestamp: var_timestamp, timestamp: var_timestamp,
amount_sat: var_amountSat, amount_sat: var_amountSat,
fees_sat: var_feesSat, fees_sat: var_feesSat,
swapper_fees_sat: var_swapperFeesSat,
payment_type: var_paymentType, payment_type: var_paymentType,
status: var_status, status: var_status,
details: var_details, details: var_details,
@@ -5453,6 +5455,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::Payment {
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.fees_sat.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(),
self.swapper_fees_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(),
self.details.into_into_dart().into_dart(), self.details.into_into_dart().into_dart(),
@@ -7413,6 +7416,7 @@ impl SseEncode for crate::model::Payment {
<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);
<u64>::sse_encode(self.fees_sat, serializer); <u64>::sse_encode(self.fees_sat, serializer);
<Option<u64>>::sse_encode(self.swapper_fees_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);
<crate::model::PaymentDetails>::sse_encode(self.details, serializer); <crate::model::PaymentDetails>::sse_encode(self.details, serializer);
@@ -9351,6 +9355,7 @@ mod io {
timestamp: self.timestamp.cst_decode(), timestamp: self.timestamp.cst_decode(),
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(),
swapper_fees_sat: self.swapper_fees_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(),
details: self.details.cst_decode(), details: self.details.cst_decode(),
@@ -10557,6 +10562,7 @@ mod io {
timestamp: Default::default(), timestamp: Default::default(),
amount_sat: Default::default(), amount_sat: Default::default(),
fees_sat: Default::default(), fees_sat: Default::default(),
swapper_fees_sat: core::ptr::null_mut(),
payment_type: Default::default(), payment_type: Default::default(),
status: Default::default(), status: Default::default(),
details: Default::default(), details: Default::default(),
@@ -12664,6 +12670,7 @@ mod io {
timestamp: u32, timestamp: u32,
amount_sat: u64, amount_sat: u64,
fees_sat: u64, fees_sat: u64,
swapper_fees_sat: *mut u64,
payment_type: i32, payment_type: i32,
status: i32, status: i32,
details: wire_cst_payment_details, details: wire_cst_payment_details,

View File

@@ -631,6 +631,8 @@ pub(crate) struct ChainSwap {
pub(crate) payer_amount_sat: u64, pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64, pub(crate) receiver_amount_sat: u64,
pub(crate) claim_fees_sat: u64, pub(crate) claim_fees_sat: u64,
/// The [ChainPair] chosen on swap creation
pub(crate) pair_fees_json: String,
pub(crate) accept_zero_conf: bool, pub(crate) accept_zero_conf: bool,
/// JSON representation of [crate::persist::chain::InternalCreateChainResponse] /// JSON representation of [crate::persist::chain::InternalCreateChainResponse]
pub(crate) create_response_json: String, pub(crate) create_response_json: String,
@@ -760,6 +762,8 @@ pub(crate) struct SendSwap {
pub(crate) preimage: Option<String>, pub(crate) preimage: Option<String>,
pub(crate) payer_amount_sat: u64, pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64, pub(crate) receiver_amount_sat: u64,
/// The [SubmarinePair] chosen on swap creation
pub(crate) pair_fees_json: String,
/// JSON representation of [crate::persist::send::InternalCreateSubmarineResponse] /// JSON representation of [crate::persist::send::InternalCreateSubmarineResponse]
pub(crate) create_response_json: String, pub(crate) create_response_json: String,
/// Persisted only when the lockup tx is successfully broadcast /// Persisted only when the lockup tx is successfully broadcast
@@ -846,6 +850,8 @@ pub(crate) struct ReceiveSwap {
/// The amount of the invoice /// The amount of the invoice
pub(crate) payer_amount_sat: u64, pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64, pub(crate) receiver_amount_sat: u64,
/// The [ReversePair] chosen on swap creation
pub(crate) pair_fees_json: String,
pub(crate) claim_fees_sat: u64, pub(crate) claim_fees_sat: u64,
/// Persisted as soon as a claim tx is broadcast /// Persisted as soon as a claim tx is broadcast
pub(crate) claim_tx_id: Option<String>, pub(crate) claim_tx_id: Option<String>,
@@ -1136,6 +1142,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,
/// The swapper service fee
pub swapper_fees_sat: u64,
pub refund_tx_id: Option<String>, pub refund_tx_id: Option<String>,
pub refund_tx_amount_sat: Option<u64>, pub refund_tx_amount_sat: Option<u64>,
@@ -1263,6 +1272,10 @@ pub struct Payment {
/// - for Receive payments, this is zero /// - for Receive payments, this is zero
pub fees_sat: u64, pub fees_sat: u64,
/// Service fees paid to the swapper service. This is only set for swaps (i.e. doesn't apply to
/// direct Liquid payments).
pub swapper_fees_sat: Option<u64>,
/// If it is a `Send` or `Receive` payment /// If it is a `Send` or `Receive` payment
pub payment_type: PaymentType, pub payment_type: PaymentType,
@@ -1290,6 +1303,7 @@ impl Payment {
timestamp: swap.created_at, timestamp: swap.created_at,
amount_sat, amount_sat,
fees_sat: swap.payer_amount_sat - swap.receiver_amount_sat, fees_sat: swap.payer_amount_sat - swap.receiver_amount_sat,
swapper_fees_sat: Some(swap.swapper_fees_sat),
payment_type, payment_type,
status: swap.status, status: swap.status,
details: PaymentDetails::Lightning { details: PaymentDetails::Lightning {
@@ -1349,6 +1363,7 @@ impl Payment {
PaymentType::Send => tx.fees_sat, PaymentType::Send => tx.fees_sat,
}, },
}, },
swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_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

@@ -35,12 +35,13 @@ impl Persister {
refund_private_key, refund_private_key,
claim_fees_sat, claim_fees_sat,
created_at, created_at,
state state,
pair_fees_json
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)?; )?;
let id_hash = sha256::Hash::hash(chain_swap.id.as_bytes()).to_hex(); let id_hash = sha256::Hash::hash(chain_swap.id.as_bytes()).to_hex();
_ = stmt.execute(( _ = stmt.execute(params![
&chain_swap.id, &chain_swap.id,
&id_hash, &id_hash,
&chain_swap.direction, &chain_swap.direction,
@@ -57,7 +58,8 @@ impl Persister {
&chain_swap.claim_fees_sat, &chain_swap.claim_fees_sat,
&chain_swap.created_at, &chain_swap.created_at,
&chain_swap.state, &chain_swap.state,
))?; &chain_swap.pair_fees_json
])?;
con.execute( con.execute(
"UPDATE chain_swaps "UPDATE chain_swaps
@@ -111,7 +113,8 @@ impl Persister {
claim_tx_id, claim_tx_id,
refund_tx_id, refund_tx_id,
created_at, created_at,
state state,
pair_fees_json
FROM chain_swaps FROM chain_swaps
{where_clause_str} {where_clause_str}
ORDER BY created_at ORDER BY created_at
@@ -160,6 +163,7 @@ impl Persister {
refund_tx_id: row.get(17)?, refund_tx_id: row.get(17)?,
created_at: row.get(18)?, created_at: row.get(18)?,
state: row.get(19)?, state: row.get(19)?,
pair_fees_json: row.get(20)?,
}) })
} }
@@ -273,7 +277,7 @@ impl Persister {
let con = self.get_connection()?; let con = self.get_connection()?;
let row_count = con let row_count = con
.execute( .execute(
"UPDATE chain_swaps "UPDATE chain_swaps
SET claim_address = :claim_address, claim_tx_id = :claim_tx_id SET claim_address = :claim_address, claim_tx_id = :claim_tx_id
WHERE id = :id AND claim_tx_id IS NULL", WHERE id = :id AND claim_tx_id IS NULL",
named_params! { named_params! {
@@ -297,7 +301,7 @@ impl Persister {
) -> Result<(), PaymentError> { ) -> Result<(), PaymentError> {
let con = self.get_connection()?; let con = self.get_connection()?;
con.execute( con.execute(
"UPDATE chain_swaps "UPDATE chain_swaps
SET claim_tx_id = NULL SET claim_tx_id = NULL
WHERE id = :id AND claim_tx_id = :claim_tx_id", WHERE id = :id AND claim_tx_id = :claim_tx_id",
named_params! { named_params! {

View File

@@ -184,5 +184,10 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
DROP TABLE old_chain_swaps; DROP TABLE old_chain_swaps;
", ",
"ALTER TABLE send_swaps ADD COLUMN bolt12_offer TEXT;", "ALTER TABLE send_swaps ADD COLUMN bolt12_offer TEXT;",
"
ALTER TABLE receive_swaps ADD COLUMN pair_fees_json TEXT NOT NULL DEFAULT '';
ALTER TABLE send_swaps ADD COLUMN pair_fees_json TEXT NOT NULL DEFAULT '';
ALTER TABLE chain_swaps ADD COLUMN pair_fees_json TEXT NOT NULL DEFAULT '';
",
] ]
} }

View File

@@ -14,6 +14,7 @@ use crate::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
use crate::model::*; use crate::model::*;
use crate::{get_invoice_description, utils}; use crate::{get_invoice_description, utils};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use boltz_client::boltz::{ChainPair, ReversePair, SubmarinePair};
use migrations::current_migrations; use migrations::current_migrations;
use rusqlite::{params, params_from_iter, Connection, OptionalExtension, Row, ToSql}; use rusqlite::{params, params_from_iter, Connection, OptionalExtension, Row, ToSql};
use rusqlite_migration::{Migrations, M}; use rusqlite_migration::{Migrations, M};
@@ -178,6 +179,7 @@ impl Persister {
rs.payer_amount_sat, rs.payer_amount_sat,
rs.receiver_amount_sat, rs.receiver_amount_sat,
rs.state, rs.state,
rs.pair_fees_json,
ss.id, ss.id,
ss.created_at, ss.created_at,
ss.invoice, ss.invoice,
@@ -189,6 +191,7 @@ impl Persister {
ss.payer_amount_sat, ss.payer_amount_sat,
ss.receiver_amount_sat, ss.receiver_amount_sat,
ss.state, ss.state,
ss.pair_fees_json,
cs.id, cs.id,
cs.created_at, cs.created_at,
cs.direction, cs.direction,
@@ -199,6 +202,7 @@ impl Persister {
cs.receiver_amount_sat, cs.receiver_amount_sat,
cs.claim_address, cs.claim_address,
cs.state, cs.state,
cs.pair_fees_json,
rtx.amount_sat, rtx.amount_sat,
pd.destination, pd.destination,
pd.description pd.description
@@ -255,106 +259,138 @@ impl Persister {
let maybe_receive_swap_payer_amount_sat: Option<u64> = row.get(12)?; let maybe_receive_swap_payer_amount_sat: Option<u64> = row.get(12)?;
let maybe_receive_swap_receiver_amount_sat: Option<u64> = row.get(13)?; let maybe_receive_swap_receiver_amount_sat: Option<u64> = row.get(13)?;
let maybe_receive_swap_receiver_state: Option<PaymentState> = row.get(14)?; let maybe_receive_swap_receiver_state: Option<PaymentState> = row.get(14)?;
let maybe_receive_swap_pair_fees_json: Option<String> = row.get(15)?;
let maybe_receive_swap_pair_fees: Option<ReversePair> =
maybe_receive_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok());
let maybe_send_swap_id: Option<String> = row.get(15)?; let maybe_send_swap_id: Option<String> = row.get(16)?;
let maybe_send_swap_created_at: Option<u32> = row.get(16)?; let maybe_send_swap_created_at: Option<u32> = row.get(17)?;
let maybe_send_swap_invoice: Option<String> = row.get(17)?; let maybe_send_swap_invoice: Option<String> = row.get(18)?;
let maybe_send_swap_bolt12_offer: Option<String> = row.get(18)?; let maybe_send_swap_bolt12_offer: Option<String> = row.get(19)?;
let maybe_send_swap_payment_hash: Option<String> = row.get(19)?; let maybe_send_swap_payment_hash: Option<String> = row.get(20)?;
let maybe_send_swap_description: Option<String> = row.get(20)?; let maybe_send_swap_description: Option<String> = row.get(21)?;
let maybe_send_swap_preimage: Option<String> = row.get(21)?; let maybe_send_swap_preimage: Option<String> = row.get(22)?;
let maybe_send_swap_refund_tx_id: Option<String> = row.get(22)?; let maybe_send_swap_refund_tx_id: Option<String> = row.get(23)?;
let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(23)?; let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(24)?;
let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(24)?; let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(25)?;
let maybe_send_swap_state: Option<PaymentState> = row.get(25)?; let maybe_send_swap_state: Option<PaymentState> = row.get(26)?;
let maybe_send_swap_pair_fees_json: Option<String> = row.get(27)?;
let maybe_send_swap_pair_fees: Option<SubmarinePair> =
maybe_send_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok());
let maybe_chain_swap_id: Option<String> = row.get(26)?; let maybe_chain_swap_id: Option<String> = row.get(28)?;
let maybe_chain_swap_created_at: Option<u32> = row.get(27)?; let maybe_chain_swap_created_at: Option<u32> = row.get(29)?;
let maybe_chain_swap_direction: Option<Direction> = row.get(28)?; let maybe_chain_swap_direction: Option<Direction> = row.get(30)?;
let maybe_chain_swap_preimage: Option<String> = row.get(29)?; let maybe_chain_swap_preimage: Option<String> = row.get(31)?;
let maybe_chain_swap_description: Option<String> = row.get(30)?; let maybe_chain_swap_description: Option<String> = row.get(32)?;
let maybe_chain_swap_refund_tx_id: Option<String> = row.get(31)?; let maybe_chain_swap_refund_tx_id: Option<String> = row.get(33)?;
let maybe_chain_swap_payer_amount_sat: Option<u64> = row.get(32)?; let maybe_chain_swap_payer_amount_sat: Option<u64> = row.get(34)?;
let maybe_chain_swap_receiver_amount_sat: Option<u64> = row.get(33)?; let maybe_chain_swap_receiver_amount_sat: Option<u64> = row.get(35)?;
let maybe_chain_swap_claim_address: Option<String> = row.get(34)?; let maybe_chain_swap_claim_address: Option<String> = row.get(36)?;
let maybe_chain_swap_state: Option<PaymentState> = row.get(35)?; let maybe_chain_swap_state: Option<PaymentState> = row.get(37)?;
let maybe_chain_swap_pair_fees_json: Option<String> = row.get(38)?;
let maybe_chain_swap_pair_fees: Option<ChainPair> =
maybe_chain_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok());
let maybe_swap_refund_tx_amount_sat: Option<u64> = row.get(36)?; let maybe_swap_refund_tx_amount_sat: Option<u64> = row.get(39)?;
let maybe_payment_details_destination: Option<String> = row.get(37)?; let maybe_payment_details_destination: Option<String> = row.get(40)?;
let maybe_payment_details_description: Option<String> = row.get(38)?; let maybe_payment_details_description: Option<String> = row.get(41)?;
let (swap, payment_type) = match maybe_receive_swap_id { let (swap, payment_type) = match maybe_receive_swap_id {
Some(receive_swap_id) => ( Some(receive_swap_id) => {
Some(PaymentSwapData { let payer_amount_sat = maybe_receive_swap_payer_amount_sat.unwrap_or(0);
swap_id: receive_swap_id,
swap_type: PaymentSwapType::Receive, (
created_at: maybe_receive_swap_created_at.unwrap_or(utils::now()),
preimage: maybe_receive_swap_preimage,
bolt11: maybe_receive_swap_invoice.clone(),
bolt12_offer: None, // Bolt12 not supported for Receive Swaps
payment_hash: maybe_receive_swap_payment_hash,
description: maybe_receive_swap_description.unwrap_or_else(|| {
maybe_receive_swap_invoice
.and_then(|bolt11| get_invoice_description!(bolt11))
.unwrap_or("Lightning payment".to_string())
}),
payer_amount_sat: maybe_receive_swap_payer_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,
claim_address: None,
status: maybe_receive_swap_receiver_state.unwrap_or(PaymentState::Created),
}),
PaymentType::Receive,
),
None => match maybe_send_swap_id {
Some(send_swap_id) => (
Some(PaymentSwapData { Some(PaymentSwapData {
swap_id: send_swap_id, swap_id: receive_swap_id,
swap_type: PaymentSwapType::Send, swap_type: PaymentSwapType::Receive,
created_at: maybe_send_swap_created_at.unwrap_or(utils::now()), created_at: maybe_receive_swap_created_at.unwrap_or(utils::now()),
preimage: maybe_send_swap_preimage, preimage: maybe_receive_swap_preimage,
bolt11: match maybe_send_swap_bolt12_offer.is_some() { bolt11: maybe_receive_swap_invoice.clone(),
true => None, // We don't expose the Bolt12 invoice bolt12_offer: None, // Bolt12 not supported for Receive Swaps
false => maybe_send_swap_invoice, payment_hash: maybe_receive_swap_payment_hash,
}, description: maybe_receive_swap_description.unwrap_or_else(|| {
bolt12_offer: maybe_send_swap_bolt12_offer, maybe_receive_swap_invoice
payment_hash: maybe_send_swap_payment_hash, .and_then(|bolt11| get_invoice_description!(bolt11))
description: maybe_send_swap_description .unwrap_or("Lightning payment".to_string())
.unwrap_or("Lightning payment".to_string()),
payer_amount_sat: maybe_send_swap_payer_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_swap_refund_tx_amount_sat,
claim_address: None,
status: maybe_send_swap_state.unwrap_or(PaymentState::Created),
}),
PaymentType::Send,
),
None => match maybe_chain_swap_id {
Some(chain_swap_id) => (
Some(PaymentSwapData {
swap_id: chain_swap_id,
swap_type: PaymentSwapType::Chain,
created_at: maybe_chain_swap_created_at.unwrap_or(utils::now()),
preimage: maybe_chain_swap_preimage,
bolt11: None,
bolt12_offer: None, // Bolt12 not supported for Chain Swaps
payment_hash: None,
description: maybe_chain_swap_description
.unwrap_or("Bitcoin transfer".to_string()),
payer_amount_sat: maybe_chain_swap_payer_amount_sat.unwrap_or(0),
receiver_amount_sat: maybe_chain_swap_receiver_amount_sat.unwrap_or(0),
refund_tx_id: maybe_chain_swap_refund_tx_id,
refund_tx_amount_sat: maybe_swap_refund_tx_amount_sat,
claim_address: maybe_chain_swap_claim_address,
status: maybe_chain_swap_state.unwrap_or(PaymentState::Created),
}), }),
maybe_chain_swap_direction payer_amount_sat,
.unwrap_or(Direction::Outgoing) receiver_amount_sat: maybe_receive_swap_receiver_amount_sat.unwrap_or(0),
.into(), swapper_fees_sat: maybe_receive_swap_pair_fees
), .map(|pair| pair.fees.boltz(payer_amount_sat))
.unwrap_or(0),
refund_tx_id: None,
refund_tx_amount_sat: None,
claim_address: None,
status: maybe_receive_swap_receiver_state.unwrap_or(PaymentState::Created),
}),
PaymentType::Receive,
)
}
None => match maybe_send_swap_id {
Some(send_swap_id) => {
let receiver_amount_sat = maybe_send_swap_receiver_amount_sat.unwrap_or(0);
(
Some(PaymentSwapData {
swap_id: send_swap_id,
swap_type: PaymentSwapType::Send,
created_at: maybe_send_swap_created_at.unwrap_or(utils::now()),
preimage: maybe_send_swap_preimage,
bolt11: match maybe_send_swap_bolt12_offer.is_some() {
true => None, // We don't expose the Bolt12 invoice
false => maybe_send_swap_invoice,
},
bolt12_offer: maybe_send_swap_bolt12_offer,
payment_hash: maybe_send_swap_payment_hash,
description: maybe_send_swap_description
.unwrap_or("Lightning payment".to_string()),
payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0),
receiver_amount_sat,
swapper_fees_sat: maybe_send_swap_pair_fees
.map(|pair| pair.fees.boltz(receiver_amount_sat))
.unwrap_or(0),
refund_tx_id: maybe_send_swap_refund_tx_id,
refund_tx_amount_sat: maybe_swap_refund_tx_amount_sat,
claim_address: None,
status: maybe_send_swap_state.unwrap_or(PaymentState::Created),
}),
PaymentType::Send,
)
}
None => match maybe_chain_swap_id {
Some(chain_swap_id) => {
let payer_amount_sat = maybe_chain_swap_payer_amount_sat.unwrap_or(0);
let swapper_fees_sat = maybe_chain_swap_pair_fees
.map(|pair| pair.fees.percentage)
.map(|fr| ((fr / 100.0) * payer_amount_sat as f64).ceil() as u64)
.unwrap_or(0);
(
Some(PaymentSwapData {
swap_id: chain_swap_id,
swap_type: PaymentSwapType::Chain,
created_at: maybe_chain_swap_created_at.unwrap_or(utils::now()),
preimage: maybe_chain_swap_preimage,
bolt11: None,
bolt12_offer: None, // Bolt12 not supported for Chain Swaps
payment_hash: None,
description: maybe_chain_swap_description
.unwrap_or("Bitcoin transfer".to_string()),
payer_amount_sat,
receiver_amount_sat: maybe_chain_swap_receiver_amount_sat
.unwrap_or(0),
swapper_fees_sat,
refund_tx_id: maybe_chain_swap_refund_tx_id,
refund_tx_amount_sat: maybe_swap_refund_tx_amount_sat,
claim_address: maybe_chain_swap_claim_address,
status: maybe_chain_swap_state.unwrap_or(PaymentState::Created),
}),
maybe_chain_swap_direction
.unwrap_or(Direction::Outgoing)
.into(),
)
}
None => (None, PaymentType::Send), None => (None, PaymentType::Send),
}, },
}, },

View File

@@ -31,9 +31,10 @@ impl Persister {
claim_fees_sat, claim_fees_sat,
mrh_address, mrh_address,
mrh_script_pubkey, mrh_script_pubkey,
state state,
pair_fees_json
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)?; )?;
let id_hash = sha256::Hash::hash(receive_swap.id.as_bytes()).to_hex(); let id_hash = sha256::Hash::hash(receive_swap.id.as_bytes()).to_hex();
_ = stmt.execute(( _ = stmt.execute((
@@ -51,6 +52,7 @@ impl Persister {
&receive_swap.mrh_address, &receive_swap.mrh_address,
&receive_swap.mrh_script_pubkey, &receive_swap.mrh_script_pubkey,
&receive_swap.state, &receive_swap.state,
&receive_swap.pair_fees_json,
))?; ))?;
con.execute( con.execute(
@@ -98,7 +100,8 @@ impl Persister {
rs.mrh_script_pubkey, rs.mrh_script_pubkey,
rs.mrh_tx_id, rs.mrh_tx_id,
rs.created_at, rs.created_at,
rs.state rs.state,
rs.pair_fees_json
FROM receive_swaps AS rs FROM receive_swaps AS rs
{where_clause_str} {where_clause_str}
ORDER BY rs.created_at ORDER BY rs.created_at
@@ -144,6 +147,7 @@ impl Persister {
mrh_tx_id: row.get(14)?, mrh_tx_id: row.get(14)?,
created_at: row.get(15)?, created_at: row.get(15)?,
state: row.get(16)?, state: row.get(16)?,
pair_fees_json: row.get(17)?,
}) })
} }

View File

@@ -31,9 +31,10 @@ impl Persister {
lockup_tx_id, lockup_tx_id,
refund_tx_id, refund_tx_id,
created_at, created_at,
state state,
pair_fees_json
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)?; )?;
let id_hash = sha256::Hash::hash(send_swap.id.as_bytes()).to_hex(); let id_hash = sha256::Hash::hash(send_swap.id.as_bytes()).to_hex();
_ = stmt.execute(( _ = stmt.execute((
@@ -51,6 +52,7 @@ impl Persister {
&send_swap.refund_tx_id, &send_swap.refund_tx_id,
&send_swap.created_at, &send_swap.created_at,
&send_swap.state, &send_swap.state,
&send_swap.pair_fees_json,
))?; ))?;
Ok(()) Ok(())
@@ -101,7 +103,8 @@ impl Persister {
lockup_tx_id, lockup_tx_id,
refund_tx_id, refund_tx_id,
created_at, created_at,
state state,
pair_fees_json
FROM send_swaps FROM send_swaps
{where_clause_str} {where_clause_str}
ORDER BY created_at ORDER BY created_at
@@ -141,6 +144,7 @@ impl Persister {
refund_tx_id: row.get(11)?, refund_tx_id: row.get(11)?,
created_at: row.get(12)?, created_at: row.get(12)?,
state: row.get(13)?, state: row.get(13)?,
pair_fees_json: row.get(14)?,
}) })
} }

View File

@@ -1239,7 +1239,7 @@ impl LiquidSdk {
to: "BTC".to_string(), to: "BTC".to_string(),
invoice: invoice.to_string(), invoice: invoice.to_string(),
refund_public_key, refund_public_key,
pair_hash: Some(lbtc_pair.hash), pair_hash: Some(lbtc_pair.hash.clone()),
referral_id: None, referral_id: None,
webhook, webhook,
})?; })?;
@@ -1258,6 +1258,9 @@ impl LiquidSdk {
preimage: None, preimage: None,
payer_amount_sat, payer_amount_sat,
receiver_amount_sat, receiver_amount_sat,
pair_fees_json: serde_json::to_string(&lbtc_pair).map_err(|e| {
PaymentError::generic(&format!("Failed to serialize SubmarinePair: {e:?}"))
})?,
create_response_json, create_response_json,
lockup_tx_id: None, lockup_tx_id: None,
refund_tx_id: None, refund_tx_id: None,
@@ -1514,7 +1517,7 @@ impl LiquidSdk {
refund_public_key: Some(refund_public_key), refund_public_key: Some(refund_public_key),
user_lock_amount: None, user_lock_amount: None,
server_lock_amount: Some(server_lockup_amount_sat), server_lock_amount: Some(server_lockup_amount_sat),
pair_hash: Some(pair.hash), pair_hash: Some(pair.hash.clone()),
referral_id: None, referral_id: None,
webhook, webhook,
})?; })?;
@@ -1537,6 +1540,9 @@ impl LiquidSdk {
payer_amount_sat, payer_amount_sat,
receiver_amount_sat, receiver_amount_sat,
claim_fees_sat, claim_fees_sat,
pair_fees_json: serde_json::to_string(&pair).map_err(|e| {
PaymentError::generic(&format!("Failed to serialize outgoing ChainPair: {e:?}"))
})?,
accept_zero_conf, accept_zero_conf,
create_response_json, create_response_json,
claim_private_key: claim_keypair.display_secret().to_string(), claim_private_key: claim_keypair.display_secret().to_string(),
@@ -1870,6 +1876,9 @@ impl LiquidSdk {
description: invoice_description, description: invoice_description,
payer_amount_sat, payer_amount_sat,
receiver_amount_sat, receiver_amount_sat,
pair_fees_json: serde_json::to_string(&reverse_pair).map_err(|e| {
PaymentError::generic(&format!("Failed to serialize ReversePair: {e:?}"))
})?,
claim_fees_sat: reverse_pair.fees.claim_estimate(), claim_fees_sat: reverse_pair.fees.claim_estimate(),
claim_tx_id: None, claim_tx_id: None,
lockup_tx_id: None, lockup_tx_id: None,
@@ -1931,7 +1940,7 @@ impl LiquidSdk {
refund_public_key: Some(refund_public_key), refund_public_key: Some(refund_public_key),
user_lock_amount: Some(user_lockup_amount_sat), user_lock_amount: Some(user_lockup_amount_sat),
server_lock_amount: None, server_lock_amount: None,
pair_hash: Some(pair.hash), pair_hash: Some(pair.hash.clone()),
referral_id: None, referral_id: None,
webhook, webhook,
})?; })?;
@@ -1954,6 +1963,9 @@ impl LiquidSdk {
payer_amount_sat: user_lockup_amount_sat, payer_amount_sat: user_lockup_amount_sat,
receiver_amount_sat, receiver_amount_sat,
claim_fees_sat, claim_fees_sat,
pair_fees_json: serde_json::to_string(&pair).map_err(|e| {
PaymentError::generic(&format!("Failed to serialize incoming ChainPair: {e:?}"))
})?,
accept_zero_conf, accept_zero_conf,
create_response_json, create_response_json,
claim_private_key: claim_keypair.display_secret().to_string(), claim_private_key: claim_keypair.display_secret().to_string(),

View File

@@ -112,6 +112,25 @@ pub(crate) fn new_chain_swap(
created_at: utils::now(), created_at: utils::now(),
state: payment_state.unwrap_or(PaymentState::Created), state: payment_state.unwrap_or(PaymentState::Created),
accept_zero_conf, accept_zero_conf,
pair_fees_json: r#"{
"hash": "43087e267db95668b9b7c48efcf44d922484870f1bdb8b926e5d6b76bf4d0709",
"rate": 1,
"limits": {
"maximal": 4294967,
"minimal": 10000,
"maximalZeroConf": 0
},
"fees": {
"percentage": 0.25,
"minerFees": {
"server": 4455,
"user": {
"claim": 3108,
"lockup": 276
}
}
}
}"#.to_string(),
}, },
Direction::Outgoing => ChainSwap { Direction::Outgoing => ChainSwap {
id: generate_random_string(4), id: generate_random_string(4),
@@ -175,6 +194,24 @@ pub(crate) fn new_chain_swap(
created_at: utils::now(), created_at: utils::now(),
state: payment_state.unwrap_or(PaymentState::Created), state: payment_state.unwrap_or(PaymentState::Created),
accept_zero_conf, accept_zero_conf,
pair_fees_json: r#"{
"hash": "3ec520412cee74863f2c75a9cd7b8d2077f68267632344ec3c4646e100883091",
"rate": 1,
"limits": {
"maximal": 4294967,
"minimal": 10000,
"maximalZeroConf": 0
},
"fees": {
"percentage": 0.25,
"minerFees": {
"server": 3384,
"user": {
"claim": 143,
"lockup": 4312
}
}
}"#.to_string(),
} }
} }
} }

View File

@@ -48,6 +48,19 @@ pub(crate) fn new_send_swap(payment_state: Option<PaymentState>) -> SendSwap {
preimage: None, preimage: None,
payer_amount_sat: 1149, payer_amount_sat: 1149,
receiver_amount_sat: 1000, receiver_amount_sat: 1000,
pair_fees_json: r#"{
"hash": "b53c0ac3da051a78f67f6dd25f2ab0858492dc6881015b236d554227c85fda7d",
"rate": 1,
"limits": {
"maximal": 25000000,
"minimal": 1000,
"maximalZeroConf": 100000
},
"fees": {
"percentage": 0.1,
"minerFees": 148
}
}"#.to_string(),
create_response_json: r#"{ create_response_json: r#"{
"accept_zero_conf": true, "accept_zero_conf": true,
"address": "tlq1pqwq5ft2l0khw7fr2f0fzfz5c00lku06sy9sgqlzhuj8y5vgslfx6y2pffw53ksu76uv25zkss8vpam96y8n2ke826mfmklaeg057guneaf8hr0ckqh0z", "address": "tlq1pqwq5ft2l0khw7fr2f0fzfz5c00lku06sy9sgqlzhuj8y5vgslfx6y2pffw53ksu76uv25zkss8vpam96y8n2ke826mfmklaeg057guneaf8hr0ckqh0z",
@@ -102,6 +115,21 @@ pub(crate) fn new_receive_swap(payment_state: Option<PaymentState>) -> ReceiveSw
payment_hash: Some("e4a0052cd7e967393b71803ef5c507b935e6e80c18de4b110b26332ad64c6fe4".to_string()), payment_hash: Some("e4a0052cd7e967393b71803ef5c507b935e6e80c18de4b110b26332ad64c6fe4".to_string()),
payer_amount_sat: 1000, payer_amount_sat: 1000,
receiver_amount_sat: 587, receiver_amount_sat: 587,
pair_fees_json: r#"{
"hash": "976e6dad9097f657213244b046e5f29524b743568a2c3d569b421df1e07e1b44",
"rate": 1,
"limits": {
"maximal": 25000000,
"minimal": 1000
},
"fees": {
"percentage": 0.25,
"minerFees": {
"claim": 143,
"lockup": 276
}
}
}"#.to_string(),
claim_fees_sat: 200, claim_fees_sat: 200,
claim_tx_id: None, claim_tx_id: None,
lockup_tx_id: None, lockup_tx_id: None,

View File

@@ -2469,16 +2469,17 @@ 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 != 9) throw Exception('unexpected arr length: expect 9 but see ${arr.length}');
return Payment( return Payment(
destination: dco_decode_opt_String(arr[0]), destination: dco_decode_opt_String(arr[0]),
txId: dco_decode_opt_String(arr[1]), txId: dco_decode_opt_String(arr[1]),
timestamp: dco_decode_u_32(arr[2]), timestamp: dco_decode_u_32(arr[2]),
amountSat: dco_decode_u_64(arr[3]), amountSat: dco_decode_u_64(arr[3]),
feesSat: dco_decode_u_64(arr[4]), feesSat: dco_decode_u_64(arr[4]),
paymentType: dco_decode_payment_type(arr[5]), swapperFeesSat: dco_decode_opt_box_autoadd_u_64(arr[5]),
status: dco_decode_payment_state(arr[6]), paymentType: dco_decode_payment_type(arr[6]),
details: dco_decode_payment_details(arr[7]), status: dco_decode_payment_state(arr[7]),
details: dco_decode_payment_details(arr[8]),
); );
} }
@@ -4444,6 +4445,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
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);
var var_feesSat = sse_decode_u_64(deserializer); var var_feesSat = sse_decode_u_64(deserializer);
var var_swapperFeesSat = 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);
var var_details = sse_decode_payment_details(deserializer); var var_details = sse_decode_payment_details(deserializer);
@@ -4453,6 +4455,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
timestamp: var_timestamp, timestamp: var_timestamp,
amountSat: var_amountSat, amountSat: var_amountSat,
feesSat: var_feesSat, feesSat: var_feesSat,
swapperFeesSat: var_swapperFeesSat,
paymentType: var_paymentType, paymentType: var_paymentType,
status: var_status, status: var_status,
details: var_details); details: var_details);
@@ -6302,6 +6305,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
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_u_64(self.feesSat, serializer); sse_encode_u_64(self.feesSat, serializer);
sse_encode_opt_box_autoadd_u_64(self.swapperFeesSat, 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);
sse_encode_payment_details(self.details, serializer); sse_encode_payment_details(self.details, serializer);

View File

@@ -2675,6 +2675,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
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.fees_sat = cst_encode_u_64(apiObj.feesSat); wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat);
wireObj.swapper_fees_sat = cst_encode_opt_box_autoadd_u_64(apiObj.swapperFeesSat);
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);
cst_api_fill_to_wire_payment_details(apiObj.details, wireObj.details); cst_api_fill_to_wire_payment_details(apiObj.details, wireObj.details);
@@ -5848,6 +5849,8 @@ final class wire_cst_payment extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int fees_sat; external int fees_sat;
external ffi.Pointer<ffi.Uint64> swapper_fees_sat;
@ffi.Int32() @ffi.Int32()
external int payment_type; external int payment_type;

View File

@@ -552,6 +552,10 @@ class Payment {
/// - for Receive payments, this is zero /// - for Receive payments, this is zero
final BigInt feesSat; final BigInt feesSat;
/// Service fees paid to the swapper service. This is only set for swaps (i.e. doesn't apply to
/// direct Liquid payments).
final BigInt? swapperFeesSat;
/// If it is a `Send` or `Receive` payment /// If it is a `Send` or `Receive` payment
final PaymentType paymentType; final PaymentType paymentType;
@@ -572,6 +576,7 @@ class Payment {
required this.timestamp, required this.timestamp,
required this.amountSat, required this.amountSat,
required this.feesSat, required this.feesSat,
this.swapperFeesSat,
required this.paymentType, required this.paymentType,
required this.status, required this.status,
required this.details, required this.details,
@@ -584,6 +589,7 @@ class Payment {
timestamp.hashCode ^ timestamp.hashCode ^
amountSat.hashCode ^ amountSat.hashCode ^
feesSat.hashCode ^ feesSat.hashCode ^
swapperFeesSat.hashCode ^
paymentType.hashCode ^ paymentType.hashCode ^
status.hashCode ^ status.hashCode ^
details.hashCode; details.hashCode;
@@ -598,6 +604,7 @@ class Payment {
timestamp == other.timestamp && timestamp == other.timestamp &&
amountSat == other.amountSat && amountSat == other.amountSat &&
feesSat == other.feesSat && feesSat == other.feesSat &&
swapperFeesSat == other.swapperFeesSat &&
paymentType == other.paymentType && paymentType == other.paymentType &&
status == other.status && status == other.status &&
details == other.details; details == other.details;

View File

@@ -4432,6 +4432,8 @@ final class wire_cst_payment extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int fees_sat; external int fees_sat;
external ffi.Pointer<ffi.Uint64> swapper_fees_sat;
@ffi.Int32() @ffi.Int32()
external int payment_type; external int payment_type;

View File

@@ -1371,9 +1371,10 @@ fun asPayment(payment: ReadableMap): Payment? {
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) }!!
val details = payment.getMap("details")?.let { asPaymentDetails(it) }!! val details = payment.getMap("details")?.let { asPaymentDetails(it) }!!
val swapperFeesSat = if (hasNonNullKey(payment, "swapperFeesSat")) payment.getDouble("swapperFeesSat").toULong() else null
val destination = if (hasNonNullKey(payment, "destination")) payment.getString("destination") else null val destination = if (hasNonNullKey(payment, "destination")) payment.getString("destination") else null
val txId = if (hasNonNullKey(payment, "txId")) payment.getString("txId") else null val txId = if (hasNonNullKey(payment, "txId")) payment.getString("txId") else null
return Payment(timestamp, amountSat, feesSat, paymentType, status, details, destination, txId) return Payment(timestamp, amountSat, feesSat, paymentType, status, details, swapperFeesSat, destination, txId)
} }
fun readableMapOf(payment: Payment): ReadableMap = fun readableMapOf(payment: Payment): ReadableMap =
@@ -1384,6 +1385,7 @@ fun readableMapOf(payment: Payment): ReadableMap =
"paymentType" to payment.paymentType.name.lowercase(), "paymentType" to payment.paymentType.name.lowercase(),
"status" to payment.status.name.lowercase(), "status" to payment.status.name.lowercase(),
"details" to readableMapOf(payment.details), "details" to readableMapOf(payment.details),
"swapperFeesSat" to payment.swapperFeesSat,
"destination" to payment.destination, "destination" to payment.destination,
"txId" to payment.txId, "txId" to payment.txId,
) )

View File

@@ -1621,6 +1621,13 @@ enum BreezSDKLiquidMapper {
} }
let details = try asPaymentDetails(paymentDetails: detailsTmp) let details = try asPaymentDetails(paymentDetails: detailsTmp)
var swapperFeesSat: UInt64?
if hasNonNilKey(data: payment, key: "swapperFeesSat") {
guard let swapperFeesSatTmp = payment["swapperFeesSat"] as? UInt64 else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "swapperFeesSat"))
}
swapperFeesSat = swapperFeesSatTmp
}
var destination: String? var destination: String?
if hasNonNilKey(data: payment, key: "destination") { if hasNonNilKey(data: payment, key: "destination") {
guard let destinationTmp = payment["destination"] as? String else { guard let destinationTmp = payment["destination"] as? String else {
@@ -1636,7 +1643,7 @@ enum BreezSDKLiquidMapper {
txId = txIdTmp txId = txIdTmp
} }
return Payment(timestamp: timestamp, amountSat: amountSat, feesSat: feesSat, paymentType: paymentType, status: status, details: details, destination: destination, txId: txId) return Payment(timestamp: timestamp, amountSat: amountSat, feesSat: feesSat, paymentType: paymentType, status: status, details: details, swapperFeesSat: swapperFeesSat, destination: destination, txId: txId)
} }
static func dictionaryOf(payment: Payment) -> [String: Any?] { static func dictionaryOf(payment: Payment) -> [String: Any?] {
@@ -1647,6 +1654,7 @@ enum BreezSDKLiquidMapper {
"paymentType": valueOf(paymentType: payment.paymentType), "paymentType": valueOf(paymentType: payment.paymentType),
"status": valueOf(paymentState: payment.status), "status": valueOf(paymentState: payment.status),
"details": dictionaryOf(paymentDetails: payment.details), "details": dictionaryOf(paymentDetails: payment.details),
"swapperFeesSat": payment.swapperFeesSat == nil ? nil : payment.swapperFeesSat,
"destination": payment.destination == nil ? nil : payment.destination, "destination": payment.destination == nil ? nil : payment.destination,
"txId": payment.txId == nil ? nil : payment.txId, "txId": payment.txId == nil ? nil : payment.txId,
] ]

View File

@@ -253,6 +253,7 @@ export interface Payment {
paymentType: PaymentType paymentType: PaymentType
status: PaymentState status: PaymentState
details: PaymentDetails details: PaymentDetails
swapperFeesSat?: number
destination?: string destination?: string
txId?: string txId?: string
} }