diff --git a/lib/bindings/langs/flutter/breez_liquid_sdk/include/breez_liquid_sdk.h b/lib/bindings/langs/flutter/breez_liquid_sdk/include/breez_liquid_sdk.h index e31e273..3a02e20 100644 --- a/lib/bindings/langs/flutter/breez_liquid_sdk/include/breez_liquid_sdk.h +++ b/lib/bindings/langs/flutter/breez_liquid_sdk/include/breez_liquid_sdk.h @@ -63,6 +63,7 @@ typedef struct wire_cst_payment { uint32_t timestamp; uint64_t amount_sat; uint64_t *fees_sat; + struct wire_cst_list_prim_u_8_strict *preimage; int32_t payment_type; int32_t status; } wire_cst_payment; diff --git a/lib/bindings/src/breez_liquid_sdk.udl b/lib/bindings/src/breez_liquid_sdk.udl index 4435a51..d4c2514 100644 --- a/lib/bindings/src/breez_liquid_sdk.udl +++ b/lib/bindings/src/breez_liquid_sdk.udl @@ -79,6 +79,7 @@ dictionary Payment { u32 timestamp; u64 amount_sat; u64? fees_sat = null; + string? preimage = null; PaymentType payment_type; PaymentState status; }; diff --git a/lib/core/src/frb/bridge.io.rs b/lib/core/src/frb/bridge.io.rs index 7ee1844..e3b4c65 100644 --- a/lib/core/src/frb/bridge.io.rs +++ b/lib/core/src/frb/bridge.io.rs @@ -172,6 +172,7 @@ impl CstDecode for wire_cst_payment { timestamp: self.timestamp.cst_decode(), amount_sat: self.amount_sat.cst_decode(), fees_sat: self.fees_sat.cst_decode(), + preimage: self.preimage.cst_decode(), payment_type: self.payment_type.cst_decode(), status: self.status.cst_decode(), } @@ -345,6 +346,7 @@ impl NewWithNullPtr for wire_cst_payment { timestamp: Default::default(), amount_sat: Default::default(), fees_sat: core::ptr::null_mut(), + preimage: core::ptr::null_mut(), payment_type: Default::default(), status: Default::default(), } @@ -693,6 +695,7 @@ pub struct wire_cst_payment { timestamp: u32, amount_sat: u64, fees_sat: *mut u64, + preimage: *mut wire_cst_list_prim_u_8_strict, payment_type: i32, status: i32, } diff --git a/lib/core/src/frb/bridge.rs b/lib/core/src/frb/bridge.rs index 64b79ce..0cb384d 100644 --- a/lib/core/src/frb/bridge.rs +++ b/lib/core/src/frb/bridge.rs @@ -592,6 +592,7 @@ impl SseDecode for crate::model::Payment { let mut var_timestamp = ::sse_decode(deserializer); let mut var_amountSat = ::sse_decode(deserializer); let mut var_feesSat = >::sse_decode(deserializer); + let mut var_preimage = >::sse_decode(deserializer); let mut var_paymentType = ::sse_decode(deserializer); let mut var_status = ::sse_decode(deserializer); return crate::model::Payment { @@ -600,6 +601,7 @@ impl SseDecode for crate::model::Payment { timestamp: var_timestamp, amount_sat: var_amountSat, fees_sat: var_feesSat, + preimage: var_preimage, payment_type: var_paymentType, status: var_status, }; @@ -936,6 +938,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::Payment { self.timestamp.into_into_dart().into_dart(), self.amount_sat.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(), + self.preimage.into_into_dart().into_dart(), self.payment_type.into_into_dart().into_dart(), self.status.into_into_dart().into_dart(), ] @@ -1289,6 +1292,7 @@ impl SseEncode for crate::model::Payment { ::sse_encode(self.timestamp, serializer); ::sse_encode(self.amount_sat, serializer); >::sse_encode(self.fees_sat, serializer); + >::sse_encode(self.preimage, serializer); ::sse_encode(self.payment_type, serializer); ::sse_encode(self.status, serializer); } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index b90a5cf..7749094 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -425,6 +425,8 @@ pub struct PaymentSwapData { /// Swap creation timestamp pub created_at: u32, + pub preimage: Option, + /// Amount sent by the swap payer pub payer_amount_sat: u64, @@ -463,6 +465,9 @@ pub struct Payment { /// received. pub fees_sat: Option, + /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). + pub preimage: Option, + pub payment_type: PaymentType, /// Composite status representing the overall status of the payment. @@ -485,6 +490,7 @@ impl Payment { fees_sat: swap .as_ref() .map(|s| s.payer_amount_sat - s.receiver_amount_sat), + preimage: swap.as_ref().and_then(|s| s.preimage.clone()), payment_type: tx.payment_type, status: match swap { Some(swap) => swap.status, diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index 6548518..5e3ee22 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -13,15 +13,16 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { state INTEGER NOT NULL ) STRICT;", "CREATE TABLE IF NOT EXISTS send_swaps ( - id TEXT NOT NULL PRIMARY KEY, - invoice TEXT NOT NULL, - payer_amount_sat INTEGER NOT NULL, - receiver_amount_sat INTEGER NOT NULL, - create_response_json TEXT NOT NULL, - lockup_tx_id TEXT, - refund_tx_id TEXT, - created_at INTEGER NOT NULL, - state INTEGER NOT NULL + id TEXT NOT NULL PRIMARY KEY, + invoice TEXT NOT NULL, + preimage TEXT, + payer_amount_sat INTEGER NOT NULL, + receiver_amount_sat INTEGER NOT NULL, + create_response_json TEXT NOT NULL, + lockup_tx_id TEXT, + refund_tx_id TEXT, + created_at INTEGER NOT NULL, + state INTEGER NOT NULL ) STRICT;", "CREATE TABLE IF NOT EXISTS payment_tx_data ( tx_id TEXT NOT NULL PRIMARY KEY, diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index abc451f..2980be7 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -112,6 +112,7 @@ impl Persister { rs.state, ss.id, ss.created_at, + ss.preimage, ss.payer_amount_sat, ss.receiver_amount_sat, ss.state @@ -140,14 +141,16 @@ impl Persister { let maybe_receive_swap_receiver_state: Option = row.get(9)?; let maybe_send_swap_id: Option = row.get(10)?; let maybe_send_swap_created_at: Option = row.get(11)?; - let maybe_send_swap_payer_amount_sat: Option = row.get(12)?; - let maybe_send_swap_receiver_amount_sat: Option = row.get(13)?; - let maybe_send_swap_state: Option = row.get(14)?; + let maybe_send_swap_preimage: Option = row.get(12)?; + let maybe_send_swap_payer_amount_sat: Option = row.get(13)?; + let maybe_send_swap_receiver_amount_sat: Option = row.get(14)?; + let maybe_send_swap_state: Option = row.get(15)?; let swap = match maybe_receive_swap_id { Some(receive_swap_id) => Some(PaymentSwapData { swap_id: receive_swap_id, created_at: maybe_receive_swap_created_at.unwrap_or(utils::now()), + preimage: None, payer_amount_sat: maybe_receive_swap_payer_amount_sat.unwrap_or(0), receiver_amount_sat: maybe_receive_swap_receiver_amount_sat.unwrap_or(0), status: maybe_receive_swap_receiver_state.unwrap_or(PaymentState::Created), @@ -155,6 +158,7 @@ impl Persister { None => maybe_send_swap_id.map(|send_swap_id| PaymentSwapData { swap_id: send_swap_id, created_at: maybe_send_swap_created_at.unwrap_or(utils::now()), + preimage: maybe_send_swap_preimage, payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0), receiver_amount_sat: maybe_send_swap_receiver_amount_sat.unwrap_or(0), status: maybe_send_swap_state.unwrap_or(PaymentState::Created), diff --git a/lib/core/src/persist/send.rs b/lib/core/src/persist/send.rs index 9de3744..10f8c1a 100644 --- a/lib/core/src/persist/send.rs +++ b/lib/core/src/persist/send.rs @@ -160,13 +160,20 @@ impl Persister { con: &Connection, swap_id: &str, to_state: PaymentState, + preimage: Option<&str>, lockup_tx_id: Option<&str>, refund_tx_id: Option<&str>, ) -> Result<(), PaymentError> { - // Do not overwrite lockup_tx_id, refund_tx_id + // Do not overwrite preimage, lockup_tx_id, refund_tx_id con.execute( "UPDATE send_swaps SET + preimage = + CASE + WHEN preimage IS NULL THEN :preimage + ELSE preimage + END, + lockup_tx_id = CASE WHEN lockup_tx_id IS NULL THEN :lockup_tx_id @@ -179,11 +186,12 @@ impl Persister { ELSE refund_tx_id END, - state=:state + state = :state WHERE id = :id", named_params! { ":id": swap_id, + ":preimage": preimage, ":lockup_tx_id": lockup_tx_id, ":refund_tx_id": refund_tx_id, ":state": to_state, diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index df7d3ab..5e95cb0 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -192,6 +192,7 @@ impl LiquidSdk { &self, swap_id: &str, to_state: PaymentState, + preimage: Option<&str>, lockup_tx_id: Option<&str>, refund_tx_id: Option<&str>, ) -> Result<(), PaymentError> { @@ -209,6 +210,7 @@ impl LiquidSdk { &con, swap_id, to_state, + preimage, lockup_tx_id, refund_tx_id, ) @@ -339,7 +341,7 @@ impl LiquidSdk { let refund_tx_id = self.try_refund(id, &swap_script, &keypair, receiver_amount_sat)?; info!("Broadcast refund tx for Swap-in {id}. Tx id: {refund_tx_id}"); - self.try_handle_send_swap_update(id, Pending, None, Some(&refund_tx_id))?; + self.try_handle_send_swap_update(id, Pending, None, None, Some(&refund_tx_id))?; Ok(()) } @@ -587,7 +589,13 @@ impl LiquidSdk { Self::verify_payment_hash(&claim_tx_response.preimage, invoice)?; // After we confirm the preimage is correct, we mark this as complete - self.try_handle_send_swap_update(swap_id, Complete, None, None)?; + self.try_handle_send_swap_update( + swap_id, + Complete, + Some(&claim_tx_response.preimage), + None, + None, + )?; let (partial_sig, pub_nonce) = refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; @@ -720,7 +728,13 @@ impl LiquidSdk { }; lockup_tx_id = self.lockup_funds(swap_id, &create_response)?; - self.try_handle_send_swap_update(swap_id, Pending, Some(&lockup_tx_id), None)?; + self.try_handle_send_swap_update( + swap_id, + Pending, + None, + Some(&lockup_tx_id), + None, + )?; } // Boltz has detected the lockup in the mempool, we can speed up @@ -962,7 +976,7 @@ impl LiquidSdk { self.try_handle_receive_swap_update(&swap.id, Complete, None)?; } if let Some(swap) = pending_send_swaps_by_refund_tx_id.get(&tx_id) { - self.try_handle_send_swap_update(&swap.id, Failed, None, None)?; + self.try_handle_send_swap_update(&swap.id, Failed, None, None, None)?; } } diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 6e0428c..82416e5 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -517,15 +517,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { Payment dco_decode_payment(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); + if (arr.length != 8) throw Exception('unexpected arr length: expect 8 but see ${arr.length}'); return Payment( txId: dco_decode_String(arr[0]), swapId: dco_decode_opt_String(arr[1]), timestamp: dco_decode_u_32(arr[2]), amountSat: dco_decode_u_64(arr[3]), feesSat: dco_decode_opt_box_autoadd_u_64(arr[4]), - paymentType: dco_decode_payment_type(arr[5]), - status: dco_decode_payment_state(arr[6]), + preimage: dco_decode_opt_String(arr[5]), + paymentType: dco_decode_payment_type(arr[6]), + status: dco_decode_payment_state(arr[7]), ); } @@ -880,6 +881,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var var_timestamp = sse_decode_u_32(deserializer); var var_amountSat = sse_decode_u_64(deserializer); var var_feesSat = sse_decode_opt_box_autoadd_u_64(deserializer); + var var_preimage = sse_decode_opt_String(deserializer); var var_paymentType = sse_decode_payment_type(deserializer); var var_status = sse_decode_payment_state(deserializer); return Payment( @@ -888,6 +890,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { timestamp: var_timestamp, amountSat: var_amountSat, feesSat: var_feesSat, + preimage: var_preimage, paymentType: var_paymentType, status: var_status); } @@ -1279,6 +1282,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_32(self.timestamp, serializer); sse_encode_u_64(self.amountSat, serializer); sse_encode_opt_box_autoadd_u_64(self.feesSat, serializer); + sse_encode_opt_String(self.preimage, serializer); sse_encode_payment_type(self.paymentType, serializer); sse_encode_payment_state(self.status, serializer); } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 177e49f..3276572 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -454,6 +454,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.timestamp = cst_encode_u_32(apiObj.timestamp); wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat); wireObj.fees_sat = cst_encode_opt_box_autoadd_u_64(apiObj.feesSat); + wireObj.preimage = cst_encode_opt_String(apiObj.preimage); wireObj.payment_type = cst_encode_payment_type(apiObj.paymentType); wireObj.status = cst_encode_payment_state(apiObj.status); } @@ -1156,6 +1157,8 @@ final class wire_cst_payment extends ffi.Struct { external ffi.Pointer fees_sat; + external ffi.Pointer preimage; + @ffi.Int32() external int payment_type; diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 2fab78e..f5e9bb4 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -111,6 +111,9 @@ class Payment { /// sender. In other words, it's the delta between the amount that was sent and the amount /// received. final int? feesSat; + + /// In case of a Send swap, this is the preimage of the paid invoice (proof of payment). + final String? preimage; final PaymentType paymentType; /// Composite status representing the overall status of the payment. @@ -126,6 +129,7 @@ class Payment { required this.timestamp, required this.amountSat, this.feesSat, + this.preimage, required this.paymentType, required this.status, }); @@ -137,6 +141,7 @@ class Payment { timestamp.hashCode ^ amountSat.hashCode ^ feesSat.hashCode ^ + preimage.hashCode ^ paymentType.hashCode ^ status.hashCode; @@ -150,6 +155,7 @@ class Payment { timestamp == other.timestamp && amountSat == other.amountSat && feesSat == other.feesSat && + preimage == other.preimage && paymentType == other.paymentType && status == other.status; } diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index beadefc..5e3139e 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -454,6 +454,8 @@ final class wire_cst_payment extends ffi.Struct { external ffi.Pointer fees_sat; + external ffi.Pointer preimage; + @ffi.Int32() external int payment_type; diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt index 110203f..0144fb6 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt @@ -140,6 +140,7 @@ fun asPayment(payment: ReadableMap): Payment? { val timestamp = payment.getInt("timestamp").toUInt() val amountSat = payment.getDouble("amountSat").toULong() val feesSat = if (hasNonNullKey(payment, "feesSat")) payment.getDouble("feesSat").toULong() else null + val preimage = if (hasNonNullKey(payment, "preimage")) payment.getString("preimage") else null val paymentType = payment.getString("paymentType")?.let { asPaymentType(it) }!! val status = payment.getString("status")?.let { asPaymentState(it) }!! return Payment( @@ -148,6 +149,7 @@ fun asPayment(payment: ReadableMap): Payment? { timestamp, amountSat, feesSat, + preimage, paymentType, status, ) @@ -160,6 +162,7 @@ fun readableMapOf(payment: Payment): ReadableMap { "timestamp" to payment.timestamp, "amountSat" to payment.amountSat, "feesSat" to payment.feesSat, + "preimage" to payment.preimage, "paymentType" to payment.paymentType.name.lowercase(), "status" to payment.status.name.lowercase(), ) diff --git a/packages/react-native/ios/BreezLiquidSDKMapper.swift b/packages/react-native/ios/BreezLiquidSDKMapper.swift index 9e0e95a..ee6e8da 100644 --- a/packages/react-native/ios/BreezLiquidSDKMapper.swift +++ b/packages/react-native/ios/BreezLiquidSDKMapper.swift @@ -155,6 +155,13 @@ enum BreezLiquidSDKMapper { } feesSat = feesSatTmp } + var preimage: String? + if hasNonNilKey(data: payment, key: "preimage") { + guard let preimageTmp = payment["preimage"] as? String else { + throw LiquidSdkError.Generic(message: errUnexpectedValue(fieldName: "preimage")) + } + preimage = preimageTmp + } guard let paymentTypeTmp = payment["paymentType"] as? String else { throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentType", typeName: "Payment")) } @@ -171,6 +178,7 @@ enum BreezLiquidSDKMapper { timestamp: timestamp, amountSat: amountSat, feesSat: feesSat, + preimage: preimage, paymentType: paymentType, status: status ) @@ -183,6 +191,7 @@ enum BreezLiquidSDKMapper { "timestamp": payment.timestamp, "amountSat": payment.amountSat, "feesSat": payment.feesSat == nil ? nil : payment.feesSat, + "preimage": payment.preimage == nil ? nil : payment.preimage, "paymentType": valueOf(paymentType: payment.paymentType), "status": valueOf(paymentState: payment.status), ] diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 4af5072..fef89d4 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -40,6 +40,7 @@ export interface Payment { timestamp: number amountSat: number feesSat?: number + preimage?: string paymentType: PaymentType status: PaymentState }