From 8c2b75334bc9f330f73c955a5f4386909bef80d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Granh=C3=A3o?= Date: Mon, 23 Oct 2023 16:58:20 +0100 Subject: [PATCH 1/2] Implement `prepare_refund()` --- libs/sdk-bindings/src/breez_sdk.udl | 14 ++ libs/sdk-bindings/src/uniffi_binding.rs | 23 +-- libs/sdk-core/src/binding.rs | 17 ++- libs/sdk-core/src/breez_services.rs | 8 ++ libs/sdk-core/src/bridge_generated.io.rs | 49 +++++++ libs/sdk-core/src/bridge_generated.rs | 29 ++++ libs/sdk-core/src/models.rs | 11 ++ libs/sdk-core/src/swap.rs | 132 +++++++++++++++--- .../ios/Classes/bridge_generated.h | 12 ++ libs/sdk-flutter/lib/bridge_generated.dart | 107 ++++++++++++++ .../main/java/com/breezsdk/BreezSDKMapper.kt | 78 +++++++++++ .../main/java/com/breezsdk/BreezSDKModule.kt | 19 +++ .../sdk-react-native/ios/BreezSDKMapper.swift | 71 ++++++++++ libs/sdk-react-native/ios/RNBreezSDK.m | 6 + libs/sdk-react-native/ios/RNBreezSDK.swift | 11 ++ libs/sdk-react-native/src/index.ts | 16 +++ tools/sdk-cli/src/command_handlers.rs | 23 ++- tools/sdk-cli/src/commands.rs | 7 + 18 files changed, 595 insertions(+), 38 deletions(-) diff --git a/libs/sdk-bindings/src/breez_sdk.udl b/libs/sdk-bindings/src/breez_sdk.udl index 8d6d07b33..170866871 100644 --- a/libs/sdk-bindings/src/breez_sdk.udl +++ b/libs/sdk-bindings/src/breez_sdk.udl @@ -580,6 +580,17 @@ dictionary SendOnchainResponse { ReverseSwapInfo reverse_swap_info; }; +dictionary PrepareRefundRequest { + string swap_address; + string to_address; + u32 sat_per_vbyte; +}; + +dictionary PrepareRefundResponse { + u32 refund_tx_weight; + u64 refund_tx_fee_sat; +}; + dictionary RefundRequest { string swap_address; string to_address; @@ -673,6 +684,9 @@ interface BlockingBreezServices { [Throws=SdkError] sequence list_refundables(); + [Throws=SdkError] + PrepareRefundResponse prepare_refund(PrepareRefundRequest req); + [Throws=SdkError] RefundResponse refund(RefundRequest req); diff --git a/libs/sdk-bindings/src/uniffi_binding.rs b/libs/sdk-bindings/src/uniffi_binding.rs index 0ad41147f..f0f43b55c 100644 --- a/libs/sdk-bindings/src/uniffi_binding.rs +++ b/libs/sdk-bindings/src/uniffi_binding.rs @@ -12,14 +12,14 @@ use breez_sdk_core::{ LocaleOverrides, LocalizedName, LogEntry, LogStream, LspInformation, MessageSuccessActionData, MetadataItem, Network, NodeConfig, NodeState, OpenChannelFeeRequest, OpenChannelFeeResponse, OpeningFeeParams, OpeningFeeParamsMenu, Payment, PaymentDetails, PaymentFailedData, - PaymentStatus, PaymentType, PaymentTypeFilter, PrepareSweepRequest, PrepareSweepResponse, Rate, - ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, RecommendedFees, - RefundRequest, RefundResponse, ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, - ReverseSwapStatus, RouteHint, RouteHintHop, SendOnchainRequest, SendOnchainResponse, - SendPaymentRequest, SendPaymentResponse, SendSpontaneousPaymentRequest, SignMessageRequest, - SignMessageResponse, StaticBackupRequest, StaticBackupResponse, SuccessActionProcessed, - SwapInfo, SwapStatus, SweepRequest, SweepResponse, Symbol, UnspentTransactionOutput, - UrlSuccessActionData, + PaymentStatus, PaymentType, PaymentTypeFilter, PrepareRefundRequest, PrepareRefundResponse, + PrepareSweepRequest, PrepareSweepResponse, Rate, ReceiveOnchainRequest, ReceivePaymentRequest, + ReceivePaymentResponse, RecommendedFees, RefundRequest, RefundResponse, ReverseSwapFeesRequest, + ReverseSwapInfo, ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, RouteHintHop, + SendOnchainRequest, SendOnchainResponse, SendPaymentRequest, SendPaymentResponse, + SendSpontaneousPaymentRequest, SignMessageRequest, SignMessageResponse, StaticBackupRequest, + StaticBackupResponse, SuccessActionProcessed, SwapInfo, SwapStatus, SweepRequest, + SweepResponse, Symbol, UnspentTransactionOutput, UrlSuccessActionData, }; use log::{Level, LevelFilter, Metadata, Record}; use once_cell::sync::{Lazy, OnceCell}; @@ -245,6 +245,13 @@ impl BlockingBreezServices { .map_err(|e| e.into()) } + // prepare a refund transaction for a failed/expired swap + // optionally used to know fees before calling `refund()` + pub fn prepare_refund(&self, req: PrepareRefundRequest) -> SdkResult { + rt().block_on(self.breez_services.prepare_refund(req)) + .map_err(|e| e.into()) + } + // construct and broadcast a refund transaction for a faile/expired swap pub fn refund(&self, req: RefundRequest) -> SdkResult { rt().block_on(self.breez_services.refund(req)) diff --git a/libs/sdk-core/src/binding.rs b/libs/sdk-core/src/binding.rs index 9608a5441..06fa4b1f5 100644 --- a/libs/sdk-core/src/binding.rs +++ b/libs/sdk-core/src/binding.rs @@ -32,12 +32,12 @@ use crate::{ BackupStatus, BuyBitcoinRequest, BuyBitcoinResponse, CheckMessageRequest, CheckMessageResponse, EnvironmentType, ListPaymentsRequest, LnUrlCallbackStatus, LnUrlPayRequest, LnUrlWithdrawRequest, LnUrlWithdrawResult, NodeConfig, OpenChannelFeeRequest, - OpenChannelFeeResponse, PrepareSweepRequest, PrepareSweepResponse, ReceiveOnchainRequest, - ReceivePaymentRequest, ReceivePaymentResponse, RefundRequest, RefundResponse, - ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, SendOnchainRequest, - SendOnchainResponse, SendPaymentRequest, SendPaymentResponse, SendSpontaneousPaymentRequest, - SignMessageRequest, SignMessageResponse, StaticBackupRequest, StaticBackupResponse, - SweepRequest, SweepResponse, + OpenChannelFeeResponse, PrepareRefundRequest, PrepareRefundResponse, PrepareSweepRequest, + PrepareSweepResponse, ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, + RefundRequest, RefundResponse, ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, + SendOnchainRequest, SendOnchainResponse, SendPaymentRequest, SendPaymentResponse, + SendSpontaneousPaymentRequest, SignMessageRequest, SignMessageResponse, StaticBackupRequest, + StaticBackupResponse, SweepRequest, SweepResponse, }; /* @@ -314,6 +314,11 @@ pub fn list_refundables() -> Result> { block_on(async { get_breez_services().await?.list_refundables().await }) } +/// See [BreezServices::prepare_refund] +pub fn prepare_refund(req: PrepareRefundRequest) -> Result { + block_on(async { get_breez_services().await?.prepare_refund(req).await }) +} + /// See [BreezServices::refund] pub fn refund(req: RefundRequest) -> Result { block_on(async { get_breez_services().await?.refund(req).await }) diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs index b956d4eba..48e5da435 100644 --- a/libs/sdk-core/src/breez_services.rs +++ b/libs/sdk-core/src/breez_services.rs @@ -682,6 +682,14 @@ impl BreezServices { self.btc_receive_swapper.list_refundables() } + /// Prepares a refund transaction for a failed/expired swap. + /// + /// Can optionally be used before [BreezServices::refund] to know how much fees will be paid + /// to perform the refund. + pub async fn prepare_refund(&self, req: PrepareRefundRequest) -> Result { + self.btc_receive_swapper.prepare_refund_swap(req).await + } + /// Construct and broadcast a refund transaction for a failed/expired swap /// /// Returns the txid of the refund transaction. diff --git a/libs/sdk-core/src/bridge_generated.io.rs b/libs/sdk-core/src/bridge_generated.io.rs index 546393469..de1bced64 100644 --- a/libs/sdk-core/src/bridge_generated.io.rs +++ b/libs/sdk-core/src/bridge_generated.io.rs @@ -199,6 +199,11 @@ pub extern "C" fn wire_list_refundables(port_: i64) { wire_list_refundables_impl(port_) } +#[no_mangle] +pub extern "C" fn wire_prepare_refund(port_: i64, req: *mut wire_PrepareRefundRequest) { + wire_prepare_refund_impl(port_, req) +} + #[no_mangle] pub extern "C" fn wire_refund(port_: i64, req: *mut wire_RefundRequest) { wire_refund_impl(port_, req) @@ -306,6 +311,11 @@ pub extern "C" fn new_box_autoadd_opening_fee_params_0() -> *mut wire_OpeningFee support::new_leak_box_ptr(wire_OpeningFeeParams::new_with_null_ptr()) } +#[no_mangle] +pub extern "C" fn new_box_autoadd_prepare_refund_request_0() -> *mut wire_PrepareRefundRequest { + support::new_leak_box_ptr(wire_PrepareRefundRequest::new_with_null_ptr()) +} + #[no_mangle] pub extern "C" fn new_box_autoadd_prepare_sweep_request_0() -> *mut wire_PrepareSweepRequest { support::new_leak_box_ptr(wire_PrepareSweepRequest::new_with_null_ptr()) @@ -484,6 +494,12 @@ impl Wire2Api for *mut wire_OpeningFeeParams { Wire2Api::::wire2api(*wrap).into() } } +impl Wire2Api for *mut wire_PrepareRefundRequest { + fn wire2api(self) -> PrepareRefundRequest { + let wrap = unsafe { support::box_from_leak_ptr(self) }; + Wire2Api::::wire2api(*wrap).into() + } +} impl Wire2Api for *mut wire_PrepareSweepRequest { fn wire2api(self) -> PrepareSweepRequest { let wrap = unsafe { support::box_from_leak_ptr(self) }; @@ -721,6 +737,15 @@ impl Wire2Api for wire_OpeningFeeParams { } } +impl Wire2Api for wire_PrepareRefundRequest { + fn wire2api(self) -> PrepareRefundRequest { + PrepareRefundRequest { + swap_address: self.swap_address.wire2api(), + to_address: self.to_address.wire2api(), + sat_per_vbyte: self.sat_per_vbyte.wire2api(), + } + } +} impl Wire2Api for wire_PrepareSweepRequest { fn wire2api(self) -> PrepareSweepRequest { PrepareSweepRequest { @@ -951,6 +976,14 @@ pub struct wire_OpeningFeeParams { promise: *mut wire_uint_8_list, } +#[repr(C)] +#[derive(Clone)] +pub struct wire_PrepareRefundRequest { + swap_address: *mut wire_uint_8_list, + to_address: *mut wire_uint_8_list, + sat_per_vbyte: u32, +} + #[repr(C)] #[derive(Clone)] pub struct wire_PrepareSweepRequest { @@ -1317,6 +1350,22 @@ impl Default for wire_OpeningFeeParams { } } +impl NewWithNullPtr for wire_PrepareRefundRequest { + fn new_with_null_ptr() -> Self { + Self { + swap_address: core::ptr::null_mut(), + to_address: core::ptr::null_mut(), + sat_per_vbyte: Default::default(), + } + } +} + +impl Default for wire_PrepareRefundRequest { + fn default() -> Self { + Self::new_with_null_ptr() + } +} + impl NewWithNullPtr for wire_PrepareSweepRequest { fn new_with_null_ptr() -> Self { Self { diff --git a/libs/sdk-core/src/bridge_generated.rs b/libs/sdk-core/src/bridge_generated.rs index 39fe66edd..1e377b140 100644 --- a/libs/sdk-core/src/bridge_generated.rs +++ b/libs/sdk-core/src/bridge_generated.rs @@ -79,6 +79,8 @@ use crate::models::PaymentDetails; use crate::models::PaymentStatus; use crate::models::PaymentType; use crate::models::PaymentTypeFilter; +use crate::models::PrepareRefundRequest; +use crate::models::PrepareRefundResponse; use crate::models::PrepareSweepRequest; use crate::models::PrepareSweepResponse; use crate::models::ReceiveOnchainRequest; @@ -593,6 +595,22 @@ fn wire_list_refundables_impl(port_: MessagePort) { move || move |task_callback| list_refundables(), ) } +fn wire_prepare_refund_impl( + port_: MessagePort, + req: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "prepare_refund", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_req = req.wire2api(); + move |task_callback| prepare_refund(api_req) + }, + ) +} fn wire_refund_impl(port_: MessagePort, req: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -1279,6 +1297,17 @@ impl support::IntoDart for PaymentType { } } impl support::IntoDartExceptPrimitive for PaymentType {} +impl support::IntoDart for PrepareRefundResponse { + fn into_dart(self) -> support::DartAbi { + vec![ + self.refund_tx_weight.into_dart(), + self.refund_tx_fee_sat.into_dart(), + ] + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for PrepareRefundResponse {} + impl support::IntoDart for PrepareSweepResponse { fn into_dart(self) -> support::DartAbi { vec![ diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs index 079b07b6d..a4b09fdb8 100644 --- a/libs/sdk-core/src/models.rs +++ b/libs/sdk-core/src/models.rs @@ -842,12 +842,23 @@ pub struct SendOnchainResponse { pub reverse_swap_info: ReverseSwapInfo, } +pub struct PrepareRefundRequest { + pub swap_address: String, + pub to_address: String, + pub sat_per_vbyte: u32, +} + pub struct RefundRequest { pub swap_address: String, pub to_address: String, pub sat_per_vbyte: u32, } +pub struct PrepareRefundResponse { + pub refund_tx_weight: u32, + pub refund_tx_fee_sat: u64, +} + pub struct RefundResponse { pub refund_tx_id: String, } diff --git a/libs/sdk-core/src/swap.rs b/libs/sdk-core/src/swap.rs index c80d9a0f9..5d3b93c86 100644 --- a/libs/sdk-core/src/swap.rs +++ b/libs/sdk-core/src/swap.rs @@ -5,8 +5,8 @@ use crate::binding::parse_invoice; use crate::chain::{get_utxos, AddressUtxos, ChainService, MempoolSpace, OnchainTx}; use crate::grpc::{AddFundInitRequest, GetSwapPaymentRequest}; use crate::{ - OpeningFeeParams, ReceivePaymentRequest, RefundRequest, RefundResponse, - SWAP_PAYMENT_FEE_EXPIRY_SECONDS, + OpeningFeeParams, PrepareRefundRequest, PrepareRefundResponse, ReceivePaymentRequest, + RefundRequest, RefundResponse, SWAP_PAYMENT_FEE_EXPIRY_SECONDS, }; use anyhow::{anyhow, Result}; use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; @@ -434,6 +434,31 @@ impl BTCReceiveSwap { self.swapper_api.complete_swap(payreq.clone()).await } + pub(crate) async fn prepare_refund_swap( + &self, + req: PrepareRefundRequest, + ) -> Result { + let swap_info = self + .persister + .get_swap_info_by_address(req.swap_address.clone())? + .ok_or_else(|| anyhow!(format!("swap address {} was not found", req.swap_address)))?; + + let transactions = self + .chain_service + .address_transactions(req.swap_address.clone()) + .await?; + let utxos = get_utxos(req.swap_address, transactions)?; + + let refund_tx = prepare_refund_tx(&utxos, req.to_address, swap_info.lock_height as u32)?; + + let refund_tx_weight = compute_refund_tx_weight(&refund_tx); + let refund_tx_fee_sat = compute_tx_fee(refund_tx_weight, req.sat_per_vbyte); + Ok(PrepareRefundResponse { + refund_tx_weight, + refund_tx_fee_sat, + }) + } + // refund_swap is the user way to receive on-chain refund for failed swaps. pub(crate) async fn refund_swap(&self, req: RefundRequest) -> Result { let swap_info = self @@ -541,22 +566,28 @@ pub(crate) fn create_submarine_swap_script( .into_script()) } -/// Creating the refund transaction that is to be used by the user in case where the swap has -/// expired. -fn create_refund_tx( - utxos: AddressUtxos, - private_key: Vec, +fn compute_refund_tx_weight(tx: &Transaction) -> u32 { + #[allow(clippy::identity_op)] // Allow "+ 0" term in sum below for clarity + let refund_witness_input_size: u32 = 1 + 1 + 73 + 1 + 0 + 1 + 100; + tx.strippedsize() as u32 * WITNESS_SCALE_FACTOR as u32 + + refund_witness_input_size * tx.input.len() as u32 +} + +fn compute_tx_fee(tx_weight: u32, sat_per_vbyte: u32) -> u64 { + (tx_weight * sat_per_vbyte / WITNESS_SCALE_FACTOR as u32) as u64 +} + +/// Prepare the refund transaction that is to be used by the user in case where the swap has +/// expired +fn prepare_refund_tx( + utxos: &AddressUtxos, to_address: String, lock_delay: u32, - input_script: &Script, - sat_per_vbyte: u32, -) -> Result> { +) -> Result { if utxos.confirmed.is_empty() { return Err(anyhow!("must have at least one input")); } - info!("creating refund tx sat_per_vbyte {}", sat_per_vbyte); - let lock_time = utxos.confirmed.iter().fold(0, |accum, item| { let confirmed_height = item.block_height.unwrap(); if accum >= confirmed_height + lock_delay { @@ -591,22 +622,37 @@ fn create_refund_tx( }]; // construct the transaction - let mut tx = Transaction { + let tx = Transaction { version: 2, lock_time: bitcoin::PackedLockTime(lock_time), input: txins.clone(), output: tx_out, }; - #[allow(clippy::identity_op)] // Allow "+ 0" term in sum below for clarity - let refund_witness_input_size: u32 = 1 + 1 + 73 + 1 + 0 + 1 + 100; - let tx_weight = tx.strippedsize() as u32 * WITNESS_SCALE_FACTOR as u32 - + refund_witness_input_size * txins.len() as u32; - let fees: u64 = (tx_weight * sat_per_vbyte / WITNESS_SCALE_FACTOR as u32) as u64; - if fees >= confirmed_amount { + Ok(tx) +} + +/// Creating the refund transaction that is to be used by the user in case where the swap has +/// expired. +fn create_refund_tx( + utxos: AddressUtxos, + private_key: Vec, + to_address: String, + lock_delay: u32, + input_script: &Script, + sat_per_vbyte: u32, +) -> Result> { + info!("creating refund tx sat_per_vbyte {}", sat_per_vbyte); + + let mut tx = prepare_refund_tx(&utxos, to_address, lock_delay)?; + + let tx_weight = compute_refund_tx_weight(&tx); + let fees = compute_tx_fee(tx_weight, sat_per_vbyte); + + if fees >= tx.output[0].value { return Err(anyhow!("insufficient funds to pay fees")); } - tx.output[0].value = confirmed_amount - fees; + tx.output[0].value -= fees; let scpt = Secp256k1::signing_only(); @@ -645,13 +691,15 @@ mod tests { use std::{sync::Arc, vec}; use anyhow::Result; + use bitcoin::consensus::deserialize; use bitcoin::hashes::{hex::FromHex, sha256}; use bitcoin::{ secp256k1::{Message, PublicKey, Secp256k1, SecretKey}, - OutPoint, Txid, + OutPoint, Transaction, Txid, }; use crate::chain::{AddressUtxos, Utxo}; + use crate::swap::{compute_refund_tx_weight, compute_tx_fee, prepare_refund_tx}; use crate::test_utils::get_test_ofp; use crate::{ breez_services::tests::get_dummy_node_state, @@ -927,6 +975,48 @@ mod tests { Ok(()) } + #[test] + fn test_prepare_refund() -> Result<()> { + // test parameters + let to_address = String::from("bc1qvhykeqcpdzu0pdvy99xnh9ckhwzcfskct6h6l2"); + let lock_time = 288; + + let utxos = AddressUtxos { + confirmed: vec![Utxo { + out: OutPoint { + txid: Txid::from_hex( + "1ab3fe9f94ff1332d6f198484c3677832d1162781f86ce85f6d7587fa97f0330", + )?, + vout: 0, + }, + value: 20000, + block_height: Some(700000), + }], + unconfirmed: vec![], + }; + + let prepared_refund_tx = prepare_refund_tx(&utxos, to_address, lock_time as u32)?; + + // Get the same `Transaction` used in `test_refund()` + let raw_tx_bytes = hex::decode("0200000000010130037fa97f58d7f685ce861f7862112d8377364c4898f1d63213ff949ffeb31a00000000002001000001204e00000000000016001465c96c830168b8f0b584294d3b9716bb8584c2d80347304402203285efcf44640551a56c53bde677988964ef1b4d11182d5d6634096042c320120220227b625f7827993aca5b9d2f4690c5e5fae44d8d42fdd5f3778ba21df8ba7c7b010064a9148a486ff2e31d6158bf39e2608864d63fefd09d5b876321024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076667022001b27521031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f68ac80af0a00").unwrap(); + let tx: Transaction = deserialize(&raw_tx_bytes).unwrap(); + let weight = Transaction::weight(&tx) as u64; + + let refund_tx_weight = compute_refund_tx_weight(&prepared_refund_tx); + assert_eq!(refund_tx_weight, weight as u32); + + let refund_tx_fee_sat = compute_tx_fee(refund_tx_weight, 0); + assert_eq!(refund_tx_fee_sat, 0); + + let refund_tx_fee_sat = compute_tx_fee(refund_tx_weight, 1); + assert_eq!(refund_tx_fee_sat, weight / 4); + + let refund_tx_fee_sat = compute_tx_fee(refund_tx_weight, 20); + assert_eq!(refund_tx_fee_sat, weight * 20 / 4); + + Ok(()) + } + #[test] fn test_refund() -> Result<()> { // test parameters diff --git a/libs/sdk-flutter/ios/Classes/bridge_generated.h b/libs/sdk-flutter/ios/Classes/bridge_generated.h index 79dad5b4b..1297ad312 100644 --- a/libs/sdk-flutter/ios/Classes/bridge_generated.h +++ b/libs/sdk-flutter/ios/Classes/bridge_generated.h @@ -178,6 +178,12 @@ typedef struct wire_PrepareSweepRequest { uint64_t sats_per_vbyte; } wire_PrepareSweepRequest; +typedef struct wire_PrepareRefundRequest { + struct wire_uint_8_list *swap_address; + struct wire_uint_8_list *to_address; + uint32_t sat_per_vbyte; +} wire_PrepareRefundRequest; + typedef struct wire_RefundRequest { struct wire_uint_8_list *swap_address; struct wire_uint_8_list *to_address; @@ -284,6 +290,8 @@ void wire_prepare_sweep(int64_t port_, struct wire_PrepareSweepRequest *req); void wire_list_refundables(int64_t port_); +void wire_prepare_refund(int64_t port_, struct wire_PrepareRefundRequest *req); + void wire_refund(int64_t port_, struct wire_RefundRequest *req); void wire_in_progress_swap(int64_t port_); @@ -326,6 +334,8 @@ struct wire_OpenChannelFeeRequest *new_box_autoadd_open_channel_fee_request_0(vo struct wire_OpeningFeeParams *new_box_autoadd_opening_fee_params_0(void); +struct wire_PrepareRefundRequest *new_box_autoadd_prepare_refund_request_0(void); + struct wire_PrepareSweepRequest *new_box_autoadd_prepare_sweep_request_0(void); struct wire_ReceiveOnchainRequest *new_box_autoadd_receive_onchain_request_0(void); @@ -400,6 +410,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) wire_sweep); dummy_var ^= ((int64_t) (void*) wire_prepare_sweep); dummy_var ^= ((int64_t) (void*) wire_list_refundables); + dummy_var ^= ((int64_t) (void*) wire_prepare_refund); dummy_var ^= ((int64_t) (void*) wire_refund); dummy_var ^= ((int64_t) (void*) wire_in_progress_swap); dummy_var ^= ((int64_t) (void*) wire_in_progress_reverse_swaps); @@ -421,6 +432,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) new_box_autoadd_node_config_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_open_channel_fee_request_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_opening_fee_params_0); + dummy_var ^= ((int64_t) (void*) new_box_autoadd_prepare_refund_request_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_prepare_sweep_request_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_receive_onchain_request_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_receive_payment_request_0); diff --git a/libs/sdk-flutter/lib/bridge_generated.dart b/libs/sdk-flutter/lib/bridge_generated.dart index 688a05845..1bd42d724 100644 --- a/libs/sdk-flutter/lib/bridge_generated.dart +++ b/libs/sdk-flutter/lib/bridge_generated.dart @@ -207,6 +207,11 @@ abstract class BreezSdkCore { FlutterRustBridgeTaskConstMeta get kListRefundablesConstMeta; + /// See [BreezServices::prepare_refund] + Future prepareRefund({required PrepareRefundRequest req, dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kPrepareRefundConstMeta; + /// See [BreezServices::refund] Future refund({required RefundRequest req, dynamic hint}); @@ -1104,6 +1109,28 @@ enum PaymentTypeFilter { ClosedChannels, } +class PrepareRefundRequest { + final String swapAddress; + final String toAddress; + final int satPerVbyte; + + const PrepareRefundRequest({ + required this.swapAddress, + required this.toAddress, + required this.satPerVbyte, + }); +} + +class PrepareRefundResponse { + final int refundTxWeight; + final int refundTxFeeSat; + + const PrepareRefundResponse({ + required this.refundTxWeight, + required this.refundTxFeeSat, + }); +} + /// We need to prepare a sweep transaction to know what fee will be charged in satoshis this /// model holds the request data which consists of the address to sweep to and the fee rate in /// satoshis per vbyte which will be converted to absolute satoshis. @@ -2221,6 +2248,22 @@ class BreezSdkCoreImpl implements BreezSdkCore { argNames: [], ); + Future prepareRefund({required PrepareRefundRequest req, dynamic hint}) { + var arg0 = _platform.api2wire_box_autoadd_prepare_refund_request(req); + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_prepare_refund(port_, arg0), + parseSuccessData: _wire2api_prepare_refund_response, + constMeta: kPrepareRefundConstMeta, + argValues: [req], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPrepareRefundConstMeta => const FlutterRustBridgeTaskConstMeta( + debugName: "prepare_refund", + argNames: ["req"], + ); + Future refund({required RefundRequest req, dynamic hint}) { var arg0 = _platform.api2wire_box_autoadd_refund_request(req); return _platform.executeNormal(FlutterRustBridgeTask( @@ -3077,6 +3120,15 @@ class BreezSdkCoreImpl implements BreezSdkCore { return PaymentType.values[raw as int]; } + PrepareRefundResponse _wire2api_prepare_refund_response(dynamic raw) { + final arr = raw as List; + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return PrepareRefundResponse( + refundTxWeight: _wire2api_u32(arr[0]), + refundTxFeeSat: _wire2api_u64(arr[1]), + ); + } + PrepareSweepResponse _wire2api_prepare_sweep_response(dynamic raw) { final arr = raw as List; if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); @@ -3489,6 +3541,14 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { return ptr; } + @protected + ffi.Pointer api2wire_box_autoadd_prepare_refund_request( + PrepareRefundRequest raw) { + final ptr = inner.new_box_autoadd_prepare_refund_request_0(); + _api_fill_to_wire_prepare_refund_request(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer api2wire_box_autoadd_prepare_sweep_request(PrepareSweepRequest raw) { final ptr = inner.new_box_autoadd_prepare_sweep_request_0(); @@ -3714,6 +3774,11 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { _api_fill_to_wire_opening_fee_params(apiObj, wireObj.ref); } + void _api_fill_to_wire_box_autoadd_prepare_refund_request( + PrepareRefundRequest apiObj, ffi.Pointer wireObj) { + _api_fill_to_wire_prepare_refund_request(apiObj, wireObj.ref); + } + void _api_fill_to_wire_box_autoadd_prepare_sweep_request( PrepareSweepRequest apiObj, ffi.Pointer wireObj) { _api_fill_to_wire_prepare_sweep_request(apiObj, wireObj.ref); @@ -3890,6 +3955,13 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { if (apiObj != null) _api_fill_to_wire_box_autoadd_opening_fee_params(apiObj, wireObj); } + void _api_fill_to_wire_prepare_refund_request( + PrepareRefundRequest apiObj, wire_PrepareRefundRequest wireObj) { + wireObj.swap_address = api2wire_String(apiObj.swapAddress); + wireObj.to_address = api2wire_String(apiObj.toAddress); + wireObj.sat_per_vbyte = api2wire_u32(apiObj.satPerVbyte); + } + void _api_fill_to_wire_prepare_sweep_request(PrepareSweepRequest apiObj, wire_PrepareSweepRequest wireObj) { wireObj.to_address = api2wire_String(apiObj.toAddress); wireObj.sats_per_vbyte = api2wire_u64(apiObj.satsPerVbyte); @@ -4586,6 +4658,22 @@ class BreezSdkCoreWire implements FlutterRustBridgeWireBase { _lookup>('wire_list_refundables'); late final _wire_list_refundables = _wire_list_refundablesPtr.asFunction(); + void wire_prepare_refund( + int port_, + ffi.Pointer req, + ) { + return _wire_prepare_refund( + port_, + req, + ); + } + + late final _wire_prepare_refundPtr = + _lookup)>>( + 'wire_prepare_refund'); + late final _wire_prepare_refund = + _wire_prepare_refundPtr.asFunction)>(); + void wire_refund( int port_, ffi.Pointer req, @@ -4830,6 +4918,16 @@ class BreezSdkCoreWire implements FlutterRustBridgeWireBase { late final _new_box_autoadd_opening_fee_params_0 = _new_box_autoadd_opening_fee_params_0Ptr.asFunction Function()>(); + ffi.Pointer new_box_autoadd_prepare_refund_request_0() { + return _new_box_autoadd_prepare_refund_request_0(); + } + + late final _new_box_autoadd_prepare_refund_request_0Ptr = + _lookup Function()>>( + 'new_box_autoadd_prepare_refund_request_0'); + late final _new_box_autoadd_prepare_refund_request_0 = _new_box_autoadd_prepare_refund_request_0Ptr + .asFunction Function()>(); + ffi.Pointer new_box_autoadd_prepare_sweep_request_0() { return _new_box_autoadd_prepare_sweep_request_0(); } @@ -5261,6 +5359,15 @@ class wire_PrepareSweepRequest extends ffi.Struct { external int sats_per_vbyte; } +class wire_PrepareRefundRequest extends ffi.Struct { + external ffi.Pointer swap_address; + + external ffi.Pointer to_address; + + @ffi.Uint32() + external int sat_per_vbyte; +} + class wire_RefundRequest extends ffi.Struct { external ffi.Pointer swap_address; diff --git a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt index 456aadea2..51ba5f0ae 100644 --- a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt +++ b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt @@ -1773,6 +1773,84 @@ fun asPaymentFailedDataList(arr: ReadableArray): List { return list } +fun asPrepareRefundRequest(prepareRefundRequest: ReadableMap): PrepareRefundRequest? { + if (!validateMandatoryFields( + prepareRefundRequest, + arrayOf( + "swapAddress", + "toAddress", + "satPerVbyte", + ), + ) + ) { + return null + } + val swapAddress = prepareRefundRequest.getString("swapAddress")!! + val toAddress = prepareRefundRequest.getString("toAddress")!! + val satPerVbyte = prepareRefundRequest.getInt("satPerVbyte").toUInt() + return PrepareRefundRequest( + swapAddress, + toAddress, + satPerVbyte, + ) +} + +fun readableMapOf(prepareRefundRequest: PrepareRefundRequest): ReadableMap { + return readableMapOf( + "swapAddress" to prepareRefundRequest.swapAddress, + "toAddress" to prepareRefundRequest.toAddress, + "satPerVbyte" to prepareRefundRequest.satPerVbyte, + ) +} + +fun asPrepareRefundRequestList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toArrayList()) { + when (value) { + is ReadableMap -> list.add(asPrepareRefundRequest(value)!!) + else -> throw IllegalArgumentException("Unsupported type ${value::class.java.name}") + } + } + return list +} + +fun asPrepareRefundResponse(prepareRefundResponse: ReadableMap): PrepareRefundResponse? { + if (!validateMandatoryFields( + prepareRefundResponse, + arrayOf( + "refundTxWeight", + "refundTxFeeSat", + ), + ) + ) { + return null + } + val refundTxWeight = prepareRefundResponse.getInt("refundTxWeight").toUInt() + val refundTxFeeSat = prepareRefundResponse.getDouble("refundTxFeeSat").toULong() + return PrepareRefundResponse( + refundTxWeight, + refundTxFeeSat, + ) +} + +fun readableMapOf(prepareRefundResponse: PrepareRefundResponse): ReadableMap { + return readableMapOf( + "refundTxWeight" to prepareRefundResponse.refundTxWeight, + "refundTxFeeSat" to prepareRefundResponse.refundTxFeeSat, + ) +} + +fun asPrepareRefundResponseList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toArrayList()) { + when (value) { + is ReadableMap -> list.add(asPrepareRefundResponse(value)!!) + else -> throw IllegalArgumentException("Unsupported type ${value::class.java.name}") + } + } + return list +} + fun asPrepareSweepRequest(prepareSweepRequest: ReadableMap): PrepareSweepRequest? { if (!validateMandatoryFields( prepareSweepRequest, diff --git a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKModule.kt b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKModule.kt index c37888e41..f5528ebee 100644 --- a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKModule.kt +++ b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKModule.kt @@ -579,6 +579,25 @@ class BreezSDKModule(reactContext: ReactApplicationContext) : ReactContextBaseJa } } + @ReactMethod + fun prepareRefund( + req: ReadableMap, + promise: Promise, + ) { + executor.execute { + try { + val prepareRefundRequest = + asPrepareRefundRequest(req) ?: run { + throw SdkException.Generic("Missing mandatory field req of type PrepareRefundRequest") + } + val res = getBreezServices().prepareRefund(prepareRefundRequest) + promise.resolve(readableMapOf(res)) + } catch (e: SdkException) { + promise.reject(e.javaClass.simpleName, e.message, e) + } + } + } + @ReactMethod fun refund( req: ReadableMap, diff --git a/libs/sdk-react-native/ios/BreezSDKMapper.swift b/libs/sdk-react-native/ios/BreezSDKMapper.swift index c82c8b219..7c92c42d6 100644 --- a/libs/sdk-react-native/ios/BreezSDKMapper.swift +++ b/libs/sdk-react-native/ios/BreezSDKMapper.swift @@ -1585,6 +1585,77 @@ class BreezSDKMapper { return paymentFailedDataList.map { v -> [String: Any?] in dictionaryOf(paymentFailedData: v) } } + static func asPrepareRefundRequest(prepareRefundRequest: [String: Any?]) throws -> PrepareRefundRequest { + guard let swapAddress = prepareRefundRequest["swapAddress"] as? String else { throw SdkError.Generic(message: "Missing mandatory field swapAddress for type PrepareRefundRequest") } + guard let toAddress = prepareRefundRequest["toAddress"] as? String else { throw SdkError.Generic(message: "Missing mandatory field toAddress for type PrepareRefundRequest") } + guard let satPerVbyte = prepareRefundRequest["satPerVbyte"] as? UInt32 else { throw SdkError.Generic(message: "Missing mandatory field satPerVbyte for type PrepareRefundRequest") } + + return PrepareRefundRequest( + swapAddress: swapAddress, + toAddress: toAddress, + satPerVbyte: satPerVbyte + ) + } + + static func dictionaryOf(prepareRefundRequest: PrepareRefundRequest) -> [String: Any?] { + return [ + "swapAddress": prepareRefundRequest.swapAddress, + "toAddress": prepareRefundRequest.toAddress, + "satPerVbyte": prepareRefundRequest.satPerVbyte, + ] + } + + static func asPrepareRefundRequestList(arr: [Any]) throws -> [PrepareRefundRequest] { + var list = [PrepareRefundRequest]() + for value in arr { + if let val = value as? [String: Any?] { + var prepareRefundRequest = try asPrepareRefundRequest(prepareRefundRequest: val) + list.append(prepareRefundRequest) + } else { + throw SdkError.Generic(message: "Invalid element type PrepareRefundRequest") + } + } + return list + } + + static func arrayOf(prepareRefundRequestList: [PrepareRefundRequest]) -> [Any] { + return prepareRefundRequestList.map { v -> [String: Any?] in dictionaryOf(prepareRefundRequest: v) } + } + + static func asPrepareRefundResponse(prepareRefundResponse: [String: Any?]) throws -> PrepareRefundResponse { + guard let refundTxWeight = prepareRefundResponse["refundTxWeight"] as? UInt32 else { throw SdkError.Generic(message: "Missing mandatory field refundTxWeight for type PrepareRefundResponse") } + guard let refundTxFeeSat = prepareRefundResponse["refundTxFeeSat"] as? UInt64 else { throw SdkError.Generic(message: "Missing mandatory field refundTxFeeSat for type PrepareRefundResponse") } + + return PrepareRefundResponse( + refundTxWeight: refundTxWeight, + refundTxFeeSat: refundTxFeeSat + ) + } + + static func dictionaryOf(prepareRefundResponse: PrepareRefundResponse) -> [String: Any?] { + return [ + "refundTxWeight": prepareRefundResponse.refundTxWeight, + "refundTxFeeSat": prepareRefundResponse.refundTxFeeSat, + ] + } + + static func asPrepareRefundResponseList(arr: [Any]) throws -> [PrepareRefundResponse] { + var list = [PrepareRefundResponse]() + for value in arr { + if let val = value as? [String: Any?] { + var prepareRefundResponse = try asPrepareRefundResponse(prepareRefundResponse: val) + list.append(prepareRefundResponse) + } else { + throw SdkError.Generic(message: "Invalid element type PrepareRefundResponse") + } + } + return list + } + + static func arrayOf(prepareRefundResponseList: [PrepareRefundResponse]) -> [Any] { + return prepareRefundResponseList.map { v -> [String: Any?] in dictionaryOf(prepareRefundResponse: v) } + } + static func asPrepareSweepRequest(prepareSweepRequest: [String: Any?]) throws -> PrepareSweepRequest { guard let toAddress = prepareSweepRequest["toAddress"] as? String else { throw SdkError.Generic(message: "Missing mandatory field toAddress for type PrepareSweepRequest") } guard let satsPerVbyte = prepareSweepRequest["satsPerVbyte"] as? UInt64 else { throw SdkError.Generic(message: "Missing mandatory field satsPerVbyte for type PrepareSweepRequest") } diff --git a/libs/sdk-react-native/ios/RNBreezSDK.m b/libs/sdk-react-native/ios/RNBreezSDK.m index ad8406856..f05845e9c 100644 --- a/libs/sdk-react-native/ios/RNBreezSDK.m +++ b/libs/sdk-react-native/ios/RNBreezSDK.m @@ -197,6 +197,12 @@ @interface RCT_EXTERN_MODULE(RNBreezSDK, RCTEventEmitter) reject: (RCTPromiseRejectBlock)reject ) +RCT_EXTERN_METHOD( + prepareRefund: (NSDictionary*)req + resolve: (RCTPromiseResolveBlock)resolve + reject: (RCTPromiseRejectBlock)reject +) + RCT_EXTERN_METHOD( refund: (NSDictionary*)req resolve: (RCTPromiseResolveBlock)resolve diff --git a/libs/sdk-react-native/ios/RNBreezSDK.swift b/libs/sdk-react-native/ios/RNBreezSDK.swift index af98d4152..a5a0cefe4 100644 --- a/libs/sdk-react-native/ios/RNBreezSDK.swift +++ b/libs/sdk-react-native/ios/RNBreezSDK.swift @@ -418,6 +418,17 @@ class RNBreezSDK: RCTEventEmitter { } } + @objc(prepareRefund:resolve:reject:) + func prepareRefund(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + do { + let prepareRefundRequest = try BreezSDKMapper.asPrepareRefundRequest(prepareRefundRequest: req) + var res = try getBreezServices().prepareRefund(req: prepareRefundRequest) + resolve(BreezSDKMapper.dictionaryOf(prepareRefundResponse: res)) + } catch let err { + rejectErr(err: err, reject: reject) + } + } + @objc(refund:resolve:reject:) func refund(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { do { diff --git a/libs/sdk-react-native/src/index.ts b/libs/sdk-react-native/src/index.ts index 7272cdb2b..0902f9038 100644 --- a/libs/sdk-react-native/src/index.ts +++ b/libs/sdk-react-native/src/index.ts @@ -286,6 +286,17 @@ export type PaymentFailedData = { invoice?: LnInvoice } +export type PrepareRefundRequest = { + swapAddress: string + toAddress: string + satPerVbyte: number +} + +export type PrepareRefundResponse = { + refundTxWeight: number + refundTxFeeSat: number +} + export type PrepareSweepRequest = { toAddress: string satsPerVbyte: number @@ -854,6 +865,11 @@ export const listRefundables = async (): Promise => { return response } +export const prepareRefund = async (req: PrepareRefundRequest): Promise => { + const response = await BreezSDK.prepareRefund(req) + return response +} + export const refund = async (req: RefundRequest): Promise => { const response = await BreezSDK.refund(req) return response diff --git a/tools/sdk-cli/src/command_handlers.rs b/tools/sdk-cli/src/command_handlers.rs index 2f397fd5d..46f4a98f3 100644 --- a/tools/sdk-cli/src/command_handlers.rs +++ b/tools/sdk-cli/src/command_handlers.rs @@ -6,9 +6,9 @@ use breez_sdk_core::InputType::{LnUrlAuth, LnUrlPay, LnUrlWithdraw}; use breez_sdk_core::{ parse, BreezEvent, BreezServices, BuyBitcoinRequest, CheckMessageRequest, EventListener, GreenlightCredentials, ListPaymentsRequest, LnUrlPayRequest, LnUrlWithdrawRequest, - ReceiveOnchainRequest, ReceivePaymentRequest, RefundRequest, ReverseSwapFeesRequest, - SendOnchainRequest, SendPaymentRequest, SendSpontaneousPaymentRequest, SignMessageRequest, - StaticBackupRequest, SweepRequest, + PrepareRefundRequest, ReceiveOnchainRequest, ReceivePaymentRequest, RefundRequest, + ReverseSwapFeesRequest, SendOnchainRequest, SendPaymentRequest, SendSpontaneousPaymentRequest, + SignMessageRequest, StaticBackupRequest, SweepRequest, }; use breez_sdk_core::{Config, GreenlightNodeConfig, NodeConfig}; use once_cell::sync::OnceCell; @@ -309,6 +309,23 @@ pub(crate) async fn handle_command( Commands::ListRefundables {} => { serde_json::to_string_pretty(&sdk()?.list_refundables().await?).map_err(|e| e.into()) } + Commands::PrepareRefund { + swap_address, + to_address, + sat_per_vbyte, + } => { + let res = sdk()? + .prepare_refund(PrepareRefundRequest { + swap_address, + to_address, + sat_per_vbyte, + }) + .await?; + Ok(format!( + "Prepared refund tx - weight: {} - fees: {} sat", + res.refund_tx_weight, res.refund_tx_fee_sat + )) + } Commands::Refund { swap_address, to_address, diff --git a/tools/sdk-cli/src/commands.rs b/tools/sdk-cli/src/commands.rs index ac828f173..4c6b32d61 100644 --- a/tools/sdk-cli/src/commands.rs +++ b/tools/sdk-cli/src/commands.rs @@ -198,6 +198,13 @@ pub(crate) enum Commands { /// List refundable swap addresses ListRefundables {}, + /// Prepare a refund transaction for an incomplete swap + PrepareRefund { + swap_address: String, + to_address: String, + sat_per_vbyte: u32, + }, + /// Broadcast a refund transaction for an incomplete swap Refund { swap_address: String, From 88207b5edfcef44cad263dbaa0e5321811379f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Granh=C3=A3o?= Date: Tue, 24 Oct 2023 11:56:51 +0100 Subject: [PATCH 2/2] Address review comments * Created `get_address_utxos` * Created `get_swap_info_ok` --- libs/sdk-core/src/swap.rs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/libs/sdk-core/src/swap.rs b/libs/sdk-core/src/swap.rs index 5d3b93c86..ac60598fc 100644 --- a/libs/sdk-core/src/swap.rs +++ b/libs/sdk-core/src/swap.rs @@ -257,11 +257,15 @@ impl BTCReceiveSwap { .collect()) } - #[allow(dead_code)] pub(crate) fn get_swap_info(&self, address: String) -> Result> { self.persister.get_swap_info_by_address(address) } + fn get_swap_info_ok(&self, address: String) -> Result { + self.get_swap_info(address.clone())? + .ok_or_else(|| anyhow!(format!("swap address {} was not found", address))) + } + pub(crate) async fn execute_pending_swaps(&self, tip: u32) -> Result<()> { // first refresh all swaps we monitor _ = self.refresh_monitored_swaps(tip).await?; @@ -438,16 +442,9 @@ impl BTCReceiveSwap { &self, req: PrepareRefundRequest, ) -> Result { - let swap_info = self - .persister - .get_swap_info_by_address(req.swap_address.clone())? - .ok_or_else(|| anyhow!(format!("swap address {} was not found", req.swap_address)))?; + let swap_info = self.get_swap_info_ok(req.swap_address.clone())?; - let transactions = self - .chain_service - .address_transactions(req.swap_address.clone()) - .await?; - let utxos = get_utxos(req.swap_address, transactions)?; + let utxos = self.get_address_utxos(req.swap_address).await?; let refund_tx = prepare_refund_tx(&utxos, req.to_address, swap_info.lock_height as u32)?; @@ -461,16 +458,9 @@ impl BTCReceiveSwap { // refund_swap is the user way to receive on-chain refund for failed swaps. pub(crate) async fn refund_swap(&self, req: RefundRequest) -> Result { - let swap_info = self - .persister - .get_swap_info_by_address(req.swap_address.clone())? - .ok_or_else(|| anyhow!(format!("swap address {} was not found", req.swap_address)))?; + let swap_info = self.get_swap_info_ok(req.swap_address.clone())?; - let transactions = self - .chain_service - .address_transactions(req.swap_address.clone()) - .await?; - let utxos = get_utxos(req.swap_address, transactions)?; + let utxos = self.get_address_utxos(req.swap_address).await?; let script = create_submarine_swap_script( swap_info.payment_hash, @@ -504,6 +494,14 @@ impl BTCReceiveSwap { refund_tx_id: tx_id, }) } + + async fn get_address_utxos(&self, address: String) -> Result { + let transactions = self + .chain_service + .address_transactions(address.clone()) + .await?; + get_utxos(address, transactions) + } } pub(crate) struct SwapKeys {