From 6b218f2e205d76daa2d6f3794a64770806df11da Mon Sep 17 00:00:00 2001 From: Ademar Alves de Oliveira Date: Mon, 3 Jul 2023 10:32:20 -0300 Subject: [PATCH] Add prepare withdraw method --- libs/Cargo.lock | 20 +- libs/sdk-bindings/src/breez_sdk.udl | 24 ++- libs/sdk-bindings/src/uniffi_binding.rs | 30 +-- libs/sdk-core/Cargo.toml | 2 +- libs/sdk-core/src/binding.rs | 20 +- libs/sdk-core/src/breez_services.rs | 28 ++- libs/sdk-core/src/bridge_generated.io.rs | 95 +++++++++- libs/sdk-core/src/bridge_generated.rs | 35 +++- libs/sdk-core/src/greenlight/node_api.rs | 171 ++++++++++++----- libs/sdk-core/src/models.rs | 49 +++-- libs/sdk-core/src/test_utils.rs | 26 ++- .../ios/Classes/bridge_generated.h | 23 ++- libs/sdk-flutter/lib/breez_bridge.dart | 17 +- libs/sdk-flutter/lib/bridge_generated.dart | 177 ++++++++++++++++-- tools/sdk-cli/Cargo.lock | 21 ++- tools/sdk-cli/src/command_handlers.rs | 34 +++- tools/sdk-cli/src/commands.rs | 11 +- 17 files changed, 633 insertions(+), 150 deletions(-) diff --git a/libs/Cargo.lock b/libs/Cargo.lock index e42ab25b7..631ab74a0 100644 --- a/libs/Cargo.lock +++ b/libs/Cargo.lock @@ -2401,9 +2401,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -2412,9 +2424,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" diff --git a/libs/sdk-bindings/src/breez_sdk.udl b/libs/sdk-bindings/src/breez_sdk.udl index 413dc1286..af6aecd60 100644 --- a/libs/sdk-bindings/src/breez_sdk.udl +++ b/libs/sdk-bindings/src/breez_sdk.udl @@ -487,7 +487,22 @@ enum BuyBitcoinProvider { "Moonpay", }; -interface BlockingBreezServices { +dictionary SweepRequest { + string to_address; + u64 sat_per_kw; +}; + +dictionary PrepareWithdrawRequest { + string to_address; + u64 fee_rate_sats_per_vbyte; +}; + +dictionary PrepareWithdrawResponse { + u64 weight; + u64 fee_sat; +}; + +interface BlockingBreezServices { [Throws=SdkError] void disconnect(); @@ -532,7 +547,7 @@ interface BlockingBreezServices { sequence list_payments(PaymentTypeFilter filter, i64? from_timestamp, i64? to_timestamp); [Throws=SdkError] - void sweep(string to_address, u64 fee_rate_sats_per_vbyte); + void sweep(SweepRequest req); [Throws=SdkError] sequence fetch_fiat_rates(); @@ -593,6 +608,9 @@ interface BlockingBreezServices { [Throws=SdkError] BuyBitcoinResponse buy_bitcoin(BuyBitcoinRequest req); + + [Throws=SdkError] + PrepareWithdrawResponse prepare_withdraw(PrepareWithdrawRequest req); }; namespace breez_sdk { @@ -613,4 +631,4 @@ namespace breez_sdk { sequence mnemonic_to_seed(string phrase); Config default_config(EnvironmentType env_type, string api_key, NodeConfig node_config); -}; \ No newline at end of file +}; diff --git a/libs/sdk-bindings/src/uniffi_binding.rs b/libs/sdk-bindings/src/uniffi_binding.rs index 46c49bf41..2998acaa3 100644 --- a/libs/sdk-bindings/src/uniffi_binding.rs +++ b/libs/sdk-bindings/src/uniffi_binding.rs @@ -16,11 +16,11 @@ use breez_sdk_core::{ LocalizedName, LogEntry, LogStream, LspInformation, MessageSuccessActionData, MetadataItem, Network, NodeConfig, NodeState, OpenChannelFeeRequest, OpenChannelFeeResponse, OpeningFeeParams, OpeningFeeParamsMenu, Payment, PaymentDetails, PaymentFailedData, - PaymentType, PaymentTypeFilter, Rate, ReceiveOnchainRequest, ReceivePaymentRequest, - ReceivePaymentResponse, RecommendedFees, ReverseSwapFeesRequest, ReverseSwapInfo, - ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, RouteHintHop, SignMessageRequest, - SignMessageResponse, SuccessActionProcessed, SwapInfo, SwapStatus, Symbol, - UnspentTransactionOutput, UrlSuccessActionData, + PaymentType, PaymentTypeFilter, PrepareWithdrawRequest, PrepareWithdrawResponse, Rate, + ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, RecommendedFees, + ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, + RouteHintHop, SignMessageRequest, SignMessageResponse, SuccessActionProcessed, SwapInfo, + SwapStatus, SweepRequest, Symbol, UnspentTransactionOutput, UrlSuccessActionData, }; static RT: Lazy = Lazy::new(|| tokio::runtime::Runtime::new().unwrap()); @@ -199,12 +199,9 @@ impl BlockingBreezServices { .map_err(|e| e.into()) } - pub fn sweep(&self, to_address: String, fee_rate_sats_per_vbyte: u64) -> SdkResult<()> { - rt().block_on( - self.breez_services - .sweep(to_address, fee_rate_sats_per_vbyte), - ) - .map_err(|e| e.into()) + pub fn sweep(&self, req: SweepRequest) -> SdkResult<()> { + rt().block_on(self.breez_services.sweep(req)) + .map_err(|e| e.into()) } pub fn fetch_fiat_rates(&self) -> SdkResult> { @@ -332,6 +329,17 @@ impl BlockingBreezServices { pub fn buy_bitcoin(&self, req: BuyBitcoinRequest) -> SdkResult { rt().block_on(self.breez_services.buy_bitcoin(req)) } + + pub fn prepare_withdraw( + &self, + prepare_withdraw_request: PrepareWithdrawRequest, + ) -> Result { + rt().block_on( + self.breez_services + .prepare_withdraw(prepare_withdraw_request), + ) + .map_err(|e| e.into()) + } } pub fn parse_invoice(invoice: String) -> SdkResult { diff --git a/libs/sdk-core/Cargo.toml b/libs/sdk-core/Cargo.toml index 81065a428..6f2691526 100644 --- a/libs/sdk-core/Cargo.toml +++ b/libs/sdk-core/Cargo.toml @@ -24,6 +24,7 @@ base64 = "0.13.0" chrono = "*" ecies = { version = "0.2", default-features = false, features = ["pure"] } env_logger = "*" +futures = "0.3.28" ripemd = "*" rand = "*" tiny-bip39 = "*" @@ -61,7 +62,6 @@ const_format = "*" miniz_oxide = "0.7.1" [dev-dependencies] -futures = "0.3.28" mockito = "0.31.1" regex = "1.8.1" tower = "0.4.13" diff --git a/libs/sdk-core/src/binding.rs b/libs/sdk-core/src/binding.rs index 51688f886..2b9782eac 100644 --- a/libs/sdk-core/src/binding.rs +++ b/libs/sdk-core/src/binding.rs @@ -32,9 +32,10 @@ use crate::lsp::LspInformation; use crate::models::{Config, LogEntry, NodeState, Payment, PaymentTypeFilter, SwapInfo}; use crate::{ BackupStatus, BuyBitcoinRequest, BuyBitcoinResponse, CheckMessageRequest, CheckMessageResponse, - EnvironmentType, LnUrlCallbackStatus, NodeConfig, ReceiveOnchainRequest, ReceivePaymentRequest, - ReceivePaymentResponse, ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, - SignMessageRequest, SignMessageResponse, + EnvironmentType, LnUrlCallbackStatus, NodeConfig, PrepareWithdrawRequest, + PrepareWithdrawResponse, ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, + ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, SignMessageRequest, + SignMessageResponse, SweepRequest, }; static BREEZ_SERVICES_INSTANCE: OnceCell> = OnceCell::new(); @@ -320,12 +321,13 @@ pub fn buy_bitcoin(req_data: BuyBitcoinRequest) -> Result { } /// See [BreezServices::sweep] -pub fn sweep(to_address: String, fee_rate_sats_per_vbyte: u64) -> Result<()> { - block_on(async { - get_breez_services()? - .sweep(to_address, fee_rate_sats_per_vbyte) - .await - }) +pub fn sweep(req: SweepRequest) -> Result<()> { + block_on(async { get_breez_services()?.sweep(req).await }) +} + +/// See [BreezServices::prepare_withdraw] +pub fn prepare_withdraw(req: PrepareWithdrawRequest) -> Result { + block_on(async { get_breez_services()?.prepare_withdraw(req).await }) } /* Refundables API's */ diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs index 72e7e1b58..8362560c7 100644 --- a/libs/sdk-core/src/breez_services.rs +++ b/libs/sdk-core/src/breez_services.rs @@ -449,15 +449,23 @@ impl BreezServices { } /// Sweep on-chain funds to the specified on-chain address, with the given feerate - pub async fn sweep(&self, to_address: String, fee_rate_sats_per_vbyte: u64) -> Result<()> { + pub async fn sweep(&self, req: SweepRequest) -> Result<()> { self.start_node().await?; - self.node_api - .sweep(to_address, fee_rate_sats_per_vbyte) - .await?; + self.node_api.sweep(req).await?; self.sync().await?; Ok(()) } + pub async fn prepare_withdraw( + &self, + req: PrepareWithdrawRequest, + ) -> Result { + self.start_node().await?; + let response = self.node_api.prepare_withdraw(req).await?; + self.sync().await?; + Ok(response) + } + /// Fetch live rates of fiat currencies pub async fn fetch_fiat_rates(&self) -> Result> { self.fiat_api.fetch_fiat_rates().await @@ -1182,7 +1190,7 @@ impl log::Log for GlobalSdkLogger { fn flush(&self) {} } -fn closed_channel_to_transaction(channel: crate::models::Channel) -> Result { +fn closed_channel_to_transaction(channel: models::Channel) -> Result { let now = SystemTime::now(); Ok(Payment { id: channel.funding_txid.clone(), @@ -1312,6 +1320,11 @@ impl BreezServicesBuilder { .unwrap_or_else(|| Arc::new(SqliteStorage::new(self.config.working_dir.clone()))); persister.init()?; + // mempool space is used to monitor the chain + let chain_service = Arc::new(MempoolSpace::from_base_url( + self.config.mempoolspace_url.clone(), + )); + let mut node_api = self.node_api.clone(); let mut backup_transport = self.backup_transport.clone(); if node_api.is_none() { @@ -1382,11 +1395,6 @@ impl BreezServicesBuilder { self.config.api_key.clone(), )); - // mempool space is used to monitor the chain - let chain_service = Arc::new(MempoolSpace::from_base_url( - self.config.mempoolspace_url.clone(), - )); - let current_lsp_id = persister.get_lsp_id()?; if current_lsp_id.is_none() && self.config.default_lsp_id.is_some() { persister.set_lsp_id(self.config.default_lsp_id.clone().unwrap())?; diff --git a/libs/sdk-core/src/bridge_generated.io.rs b/libs/sdk-core/src/bridge_generated.io.rs index 2a957ae1a..ea9dde54d 100644 --- a/libs/sdk-core/src/bridge_generated.io.rs +++ b/libs/sdk-core/src/bridge_generated.io.rs @@ -212,12 +212,13 @@ pub extern "C" fn wire_buy_bitcoin(port_: i64, req_data: *mut wire_BuyBitcoinReq } #[no_mangle] -pub extern "C" fn wire_sweep( - port_: i64, - to_address: *mut wire_uint_8_list, - fee_rate_sats_per_vbyte: u64, -) { - wire_sweep_impl(port_, to_address, fee_rate_sats_per_vbyte) +pub extern "C" fn wire_sweep(port_: i64, req: *mut wire_SweepRequest) { + wire_sweep_impl(port_, req) +} + +#[no_mangle] +pub extern "C" fn wire_prepare_withdraw(port_: i64, req: *mut wire_PrepareWithdrawRequest) { + wire_prepare_withdraw_impl(port_, req) } #[no_mangle] @@ -323,6 +324,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_withdraw_request_0() -> *mut wire_PrepareWithdrawRequest { + support::new_leak_box_ptr(wire_PrepareWithdrawRequest::new_with_null_ptr()) +} + #[no_mangle] pub extern "C" fn new_box_autoadd_receive_onchain_request_0() -> *mut wire_ReceiveOnchainRequest { support::new_leak_box_ptr(wire_ReceiveOnchainRequest::new_with_null_ptr()) @@ -344,6 +350,11 @@ pub extern "C" fn new_box_autoadd_sign_message_request_0() -> *mut wire_SignMess support::new_leak_box_ptr(wire_SignMessageRequest::new_with_null_ptr()) } +#[no_mangle] +pub extern "C" fn new_box_autoadd_sweep_request_0() -> *mut wire_SweepRequest { + support::new_leak_box_ptr(wire_SweepRequest::new_with_null_ptr()) +} + #[no_mangle] pub extern "C" fn new_box_autoadd_u32_0(value: u32) -> *mut u32 { support::new_leak_box_ptr(value) @@ -444,6 +455,12 @@ impl Wire2Api for *mut wire_OpeningFeeParams { Wire2Api::::wire2api(*wrap).into() } } +impl Wire2Api for *mut wire_PrepareWithdrawRequest { + fn wire2api(self) -> PrepareWithdrawRequest { + let wrap = unsafe { support::box_from_leak_ptr(self) }; + Wire2Api::::wire2api(*wrap).into() + } +} impl Wire2Api for *mut wire_ReceiveOnchainRequest { fn wire2api(self) -> ReceiveOnchainRequest { let wrap = unsafe { support::box_from_leak_ptr(self) }; @@ -468,6 +485,12 @@ impl Wire2Api for *mut wire_SignMessageRequest { Wire2Api::::wire2api(*wrap).into() } } +impl Wire2Api for *mut wire_SweepRequest { + fn wire2api(self) -> SweepRequest { + let wrap = unsafe { support::box_from_leak_ptr(self) }; + Wire2Api::::wire2api(*wrap).into() + } +} impl Wire2Api for *mut u32 { fn wire2api(self) -> u32 { unsafe { *support::box_from_leak_ptr(self) } @@ -592,6 +615,14 @@ impl Wire2Api for wire_OpeningFeeParams { } } +impl Wire2Api for wire_PrepareWithdrawRequest { + fn wire2api(self) -> PrepareWithdrawRequest { + PrepareWithdrawRequest { + to_address: self.to_address.wire2api(), + fee_rate_sats_per_vbyte: self.fee_rate_sats_per_vbyte.wire2api(), + } + } +} impl Wire2Api for wire_ReceiveOnchainRequest { fn wire2api(self) -> ReceiveOnchainRequest { ReceiveOnchainRequest { @@ -626,6 +657,14 @@ impl Wire2Api for wire_SignMessageRequest { } } } +impl Wire2Api for wire_SweepRequest { + fn wire2api(self) -> SweepRequest { + SweepRequest { + to_address: self.to_address.wire2api(), + sat_per_kw: self.sat_per_kw.wire2api(), + } + } +} impl Wire2Api> for *mut wire_uint_8_list { fn wire2api(self) -> Vec { @@ -723,6 +762,13 @@ pub struct wire_OpeningFeeParams { promise: *mut wire_uint_8_list, } +#[repr(C)] +#[derive(Clone)] +pub struct wire_PrepareWithdrawRequest { + to_address: *mut wire_uint_8_list, + fee_rate_sats_per_vbyte: u64, +} + #[repr(C)] #[derive(Clone)] pub struct wire_ReceiveOnchainRequest { @@ -753,6 +799,13 @@ pub struct wire_SignMessageRequest { message: *mut wire_uint_8_list, } +#[repr(C)] +#[derive(Clone)] +pub struct wire_SweepRequest { + to_address: *mut wire_uint_8_list, + sat_per_kw: u64, +} + #[repr(C)] #[derive(Clone)] pub struct wire_uint_8_list { @@ -972,6 +1025,21 @@ impl Default for wire_OpeningFeeParams { } } +impl NewWithNullPtr for wire_PrepareWithdrawRequest { + fn new_with_null_ptr() -> Self { + Self { + to_address: core::ptr::null_mut(), + fee_rate_sats_per_vbyte: Default::default(), + } + } +} + +impl Default for wire_PrepareWithdrawRequest { + fn default() -> Self { + Self::new_with_null_ptr() + } +} + impl NewWithNullPtr for wire_ReceiveOnchainRequest { fn new_with_null_ptr() -> Self { Self { @@ -1034,6 +1102,21 @@ impl Default for wire_SignMessageRequest { } } +impl NewWithNullPtr for wire_SweepRequest { + fn new_with_null_ptr() -> Self { + Self { + to_address: core::ptr::null_mut(), + sat_per_kw: Default::default(), + } + } +} + +impl Default for wire_SweepRequest { + fn default() -> Self { + Self::new_with_null_ptr() + } +} + // Section: sync execution mode utility #[no_mangle] diff --git a/libs/sdk-core/src/bridge_generated.rs b/libs/sdk-core/src/bridge_generated.rs index cfe382b94..2b0561915 100644 --- a/libs/sdk-core/src/bridge_generated.rs +++ b/libs/sdk-core/src/bridge_generated.rs @@ -71,6 +71,8 @@ use crate::models::Payment; use crate::models::PaymentDetails; use crate::models::PaymentType; use crate::models::PaymentTypeFilter; +use crate::models::PrepareWithdrawRequest; +use crate::models::PrepareWithdrawResponse; use crate::models::ReceiveOnchainRequest; use crate::models::ReceivePaymentRequest; use crate::models::ReceivePaymentResponse; @@ -80,6 +82,7 @@ use crate::models::ReverseSwapPairInfo; use crate::models::ReverseSwapStatus; use crate::models::SwapInfo; use crate::models::SwapStatus; +use crate::models::SweepRequest; use crate::models::UnspentTransactionOutput; // Section: wire functions @@ -561,21 +564,32 @@ fn wire_buy_bitcoin_impl( }, ) } -fn wire_sweep_impl( +fn wire_sweep_impl(port_: MessagePort, req: impl Wire2Api + UnwindSafe) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "sweep", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_req = req.wire2api(); + move |task_callback| sweep(api_req) + }, + ) +} +fn wire_prepare_withdraw_impl( port_: MessagePort, - to_address: impl Wire2Api + UnwindSafe, - fee_rate_sats_per_vbyte: impl Wire2Api + UnwindSafe, + req: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { - debug_name: "sweep", + debug_name: "prepare_withdraw", port: Some(port_), mode: FfiCallMode::Normal, }, move || { - let api_to_address = to_address.wire2api(); - let api_fee_rate_sats_per_vbyte = fee_rate_sats_per_vbyte.wire2api(); - move |task_callback| sweep(api_to_address, api_fee_rate_sats_per_vbyte) + let api_req = req.wire2api(); + move |task_callback| prepare_withdraw(api_req) }, ) } @@ -1229,6 +1243,13 @@ impl support::IntoDart for PaymentType { } } impl support::IntoDartExceptPrimitive for PaymentType {} +impl support::IntoDart for PrepareWithdrawResponse { + fn into_dart(self) -> support::DartAbi { + vec![self.weight.into_dart(), self.fee_sat.into_dart()].into_dart() + } +} +impl support::IntoDartExceptPrimitive for PrepareWithdrawResponse {} + impl support::IntoDart for Rate { fn into_dart(self) -> support::DartAbi { vec![self.coin.into_dart(), self.value.into_dart()].into_dart() diff --git a/libs/sdk-core/src/greenlight/node_api.rs b/libs/sdk-core/src/greenlight/node_api.rs index ec11e9c5a..7ef3ec598 100644 --- a/libs/sdk-core/src/greenlight/node_api.rs +++ b/libs/sdk-core/src/greenlight/node_api.rs @@ -5,10 +5,13 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{anyhow, Result}; use bitcoin::bech32::{u5, ToBase32}; +use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; +use bitcoin::hashes::Hash; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::Secp256k1; use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}; +use bitcoin::{Address, OutPoint, Script, Sequence, Transaction, TxIn, TxOut, Txid, Witness}; use ecies::utils::{aes_decrypt, aes_encrypt}; use gl_client::node::ClnClient; use gl_client::pb::amount::Unit; @@ -17,8 +20,9 @@ use gl_client::pb::cln::{ ListpeerchannelsRequest, }; use gl_client::pb::cln::{AmountOrAny, InvoiceRequest}; -use gl_client::pb::{Amount, InvoiceStatus, OffChainPayment, PayStatus, Peer, WithdrawResponse}; - +use gl_client::pb::{ + Amount, InvoiceStatus, ListFundsResponse, OffChainPayment, PayStatus, Peer, WithdrawResponse, +}; use gl_client::scheduler::Scheduler; use gl_client::signer::Signer; use gl_client::tls::TlsConfig; @@ -34,7 +38,7 @@ use tonic::Streaming; use crate::invoice::parse_invoice; use crate::models::*; use crate::persist::db::SqliteStorage; -use crate::{Channel, ChannelState, NodeConfig}; +use crate::{Channel, ChannelState, NodeConfig, PrepareWithdrawRequest, PrepareWithdrawResponse}; const MAX_PAYMENT_AMOUNT_MSAT: u64 = 4294967000; const MAX_INBOUND_LIQUIDITY_MSAT: u64 = 4000000000; @@ -54,7 +58,7 @@ impl Greenlight { /// If the node is not created, it will register it using the provided partner credentials /// or invite code /// If the node is already registered and an existing credentials were found, it will try to - /// connect to the node using these credentials. + /// connect to the node using these credentials. pub async fn connect( config: Config, seed: Vec, @@ -344,7 +348,7 @@ impl NodeAPI for Greenlight { Ok(res.bolt11) } - // implemenet pull changes from greenlight + // implement pull changes from greenlight async fn pull_changed( &self, since_timestamp: i64, @@ -361,11 +365,7 @@ impl NodeAPI for Greenlight { .into_inner(); // list both off chain funds and on chain fudns - let funds = client - .list_funds(pb::ListFundsRequest::default()) - .await? - .into_inner(); - let onchain_funds = funds.outputs; + let funds = list_funds(self).await?; // Fetch closed channels from greenlight let closed_channels = match node_client @@ -417,34 +417,8 @@ impl NodeAPI for Greenlight { all_channel_models.extend(forgotten_closed_channels?); // calculate onchain balance - let onchain_balance = onchain_funds.iter().fold(0, |a, b| { - if b.reserved { - return a; - } - a + amount_to_msat(&b.amount.clone().unwrap_or_default()) - }); - - // Collect utxos from onchain funds - let utxos = onchain_funds - .iter() - .filter_map(|list_funds_output| { - list_funds_output - .output - .as_ref() - .map(|output| UnspentTransactionOutput { - txid: output.txid.clone(), - outnum: output.outnum, - amount_millisatoshi: list_funds_output - .amount - .as_ref() - .map(amount_to_msat) - .unwrap_or_default(), - address: list_funds_output.address.clone(), - reserved: list_funds_output.reserved, - reserved_to_block: list_funds_output.reserved_to_block, - }) - }) - .collect(); + let onchain_balance = self.on_chain_balance(Some(funds.clone())).await?; + let utxos = utxos(self, Some(funds.clone())).await?; // calculate payment limits and inbound liquidity let mut max_payable: u64 = 0; @@ -546,21 +520,17 @@ impl NodeAPI for Greenlight { Ok(()) } - async fn sweep( - &self, - to_address: String, - fee_rate_sats_per_vbyte: u64, - ) -> Result { + async fn sweep(&self, req: SweepRequest) -> Result { let mut client = self.get_client().await?; let request = pb::WithdrawRequest { feerate: Some(pb::Feerate { - value: Some(pb::feerate::Value::Perkw(fee_rate_sats_per_vbyte * 250)), + value: Some(pb::feerate::Value::Perkw(req.sat_per_kw * 250)), }), amount: Some(Amount { unit: Some(Unit::All(true)), }), - destination: to_address, + destination: req.to_address, minconf: None, utxos: vec![], }; @@ -568,6 +538,61 @@ impl NodeAPI for Greenlight { Ok(client.withdraw(request).await?.into_inner()) } + async fn prepare_withdraw( + &self, + req: PrepareWithdrawRequest, + ) -> Result { + let funds = list_funds(self).await?; + let utxos = utxos(self, Some(funds)).await?; + + let mut amount: u64 = 0; + let txins: Vec = utxos + .iter() + .map(|utxo| { + amount += utxo.amount_millisatoshi; + TxIn { + previous_output: OutPoint { + txid: Txid::from_slice(&utxo.txid).unwrap(), + vout: 0, + }, + script_sig: Script::new(), + sequence: Sequence(0), + witness: Witness::default(), + } + }) + .collect(); + + // remove dust + amount /= 1000; + amount *= 1000; + + let btc_address = Address::from_str(&req.to_address)?; + let tx_out: Vec = vec![TxOut { + value: amount, + script_pubkey: btc_address.payload.script_pubkey(), + }]; + let mut tx = Transaction { + version: 2, + lock_time: bitcoin::PackedLockTime(0), + input: txins.clone(), + output: tx_out, + }; + + let witness_input_size: u64 = 110; + let tx_weight = tx.strippedsize() as u64 * WITNESS_SCALE_FACTOR as u64 + + witness_input_size * txins.len() as u64; + let fee: u64 = tx_weight * req.fee_rate_sats_per_vbyte / WITNESS_SCALE_FACTOR as u64; + if fee >= amount { + return Err(anyhow!("insufficient funds to pay fees")); + } + tx.output[0].value = amount - fee; + + return Ok(PrepareWithdrawResponse { + weight: tx_weight, + fee_sat: fee, + }); + } + /// Starts the signer that listens in a loop until the shutdown signal is received async fn start_signer(&self, shutdown: mpsc::Receiver<()>) { match self.signer.run_forever(shutdown).await { @@ -784,6 +809,62 @@ impl NodeAPI for Greenlight { fn legacy_derive_bip32_key(&self, path: Vec) -> Result { Self::legacy_derive_bip32_key(self.sdk_config.network, &self.signer, path) } + + async fn on_chain_balance(&self, funds: Option) -> Result { + let funds = match funds { + Some(f) => f, + None => list_funds(self).await?, + }; + let on_chain_balance = funds.outputs.iter().fold(0, |a, b| { + if b.reserved { + return a; + } + a + amount_to_msat(&b.amount.clone().unwrap_or_default()) + }); + return Ok(on_chain_balance); + } +} + +async fn list_funds(node: &Greenlight) -> Result { + let mut client = node.get_client().await?; + let funds = client + .list_funds(pb::ListFundsRequest::default()) + .await? + .into_inner(); + Ok(funds) +} + +// Collect utxos from onchain funds +async fn utxos( + node: &Greenlight, + funds: Option, +) -> Result> { + let funds = match funds { + Some(f) => f, + None => list_funds(node).await?, + }; + let utxos: Vec = funds + .outputs + .iter() + .filter_map(|list_funds_output| { + list_funds_output + .output + .as_ref() + .map(|output| UnspentTransactionOutput { + txid: output.txid.clone(), + outnum: output.outnum, + amount_millisatoshi: list_funds_output + .amount + .as_ref() + .map(amount_to_msat) + .unwrap_or_default(), + address: list_funds_output.address.clone(), + reserved: list_funds_output.reserved, + reserved_to_block: list_funds_output.reserved_to_block, + }) + }) + .collect(); + Ok(utxos) } #[derive(Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize)] diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs index 56324cdce..405130140 100644 --- a/libs/sdk-core/src/models.rs +++ b/libs/sdk-core/src/models.rs @@ -1,3 +1,4 @@ +use std::cmp::max; use std::ops::Add; use std::str::FromStr; @@ -10,19 +11,18 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}; use bitcoin::{Address, Script}; use chrono::{DateTime, Duration, Utc}; -use gl_client::pb::WithdrawResponse; +use gl_client::pb::{ListFundsResponse, WithdrawResponse}; +use gl_client::signer::model::greenlight::Peer; use lightning_invoice::RawInvoice; use ripemd::Digest; use ripemd::Ripemd160; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::ToSql; use serde::{Deserialize, Serialize}; -use std::cmp::max; +use strum_macros::{Display, EnumString}; use tokio::sync::mpsc; use tonic::Streaming; -use gl_client::signer::model::greenlight::Peer; - use crate::boltzswap::{BoltzApiCreateReverseSwapResponse, BoltzApiReverseSwapStatus}; use crate::fiat::{FiatCurrency, Rate}; use crate::grpc::{self, GetReverseRoutingNodeRequest, PaymentInformation, RegisterPaymentReply}; @@ -33,7 +33,6 @@ use crate::{LNInvoice, LnUrlErrorData}; use crate::breez_services::BreezServer; use crate::error::{SdkError, SdkResult}; -use strum_macros::{Display, EnumString}; pub const SWAP_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60 * 24 * 2; // 2 days pub const INVOICE_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60; // 60 minutes @@ -68,18 +67,18 @@ pub trait NodeAPI: Send + Sync { &self, bolt11: String, amount_sats: Option, - ) -> Result; + ) -> Result; async fn send_spontaneous_payment( &self, node_id: String, amount_sats: u64, - ) -> Result; + ) -> Result; async fn start(&self) -> Result<()>; - async fn sweep( + async fn sweep(&self, req: SweepRequest) -> Result; + async fn prepare_withdraw( &self, - to_address: String, - fee_rate_sats_per_vbyte: u64, - ) -> Result; + req: PrepareWithdrawRequest, + ) -> Result; async fn start_signer(&self, shutdown: mpsc::Receiver<()>); async fn list_peers(&self) -> Result>; async fn connect_peer(&self, node_id: String, addr: String) -> Result<()>; @@ -94,6 +93,7 @@ pub trait NodeAPI: Send + Sync { /// Gets the private key at the path specified fn derive_bip32_key(&self, path: Vec) -> Result; fn legacy_derive_bip32_key(&self, path: Vec) -> Result; + async fn on_chain_balance(&self, funds: Option) -> Result; } /// Trait covering LSP-related functionality @@ -1092,6 +1092,30 @@ pub enum BuyBitcoinProvider { Moonpay, } +/// Sweep options: The bitcoin address aimed to receive and the fee to be paid +#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)] +pub struct SweepRequest { + pub to_address: String, + pub sat_per_kw: u64, +} + +/// We need to prepare a withdraw transaction to know what fee will be charged in satoshis this +/// model holds the request data which consists of the address to withdraw to and the fee rate in +/// satoshis per vbyte which will be converted to absolute satoshis. +#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)] +pub struct PrepareWithdrawRequest { + pub to_address: String, + pub fee_rate_sats_per_vbyte: u64, +} + +/// We need to prepare a withdraw transaction to know what a fee it will be charged in satoshis +/// this model holds the response data, which consists of the weight and the absolute fee in sats +#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)] +pub struct PrepareWithdrawResponse { + pub weight: u64, + pub fee_sat: u64, +} + impl FromStr for BuyBitcoinProvider { type Err = anyhow::Error; @@ -1105,7 +1129,6 @@ impl FromStr for BuyBitcoinProvider { #[cfg(test)] mod tests { - use super::OpeningFeeParamsMenu; use anyhow::Result; use prost::Message; use rand::random; @@ -1114,6 +1137,8 @@ mod tests { use crate::test_utils::{get_test_ofp, rand_vec_u8}; use crate::OpeningFeeParams; + use super::OpeningFeeParamsMenu; + #[test] fn test_ofp_menu_validation() -> Result<()> { // Menu with one entry is valid diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs index 0a074fc95..374369012 100644 --- a/libs/sdk-core/src/test_utils.rs +++ b/libs/sdk-core/src/test_utils.rs @@ -11,7 +11,7 @@ use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}; use bitcoin::Network; use chrono::{SecondsFormat, Utc}; use gl_client::pb::amount::Unit; -use gl_client::pb::{Amount, Peer, WithdrawResponse}; +use gl_client::pb::{Amount, ListFundsResponse, Peer, WithdrawResponse}; use lightning::ln::PaymentSecret; use lightning_invoice::{Currency, InvoiceBuilder, RawInvoice}; use rand::distributions::{Alphanumeric, DistString, Standard}; @@ -31,9 +31,12 @@ use crate::lsp::LspInformation; use crate::models::{FiatAPI, LspAPI, NodeAPI, NodeState, Payment, Swap, SwapperAPI, SyncResponse}; use crate::moonpay::MoonPayApi; use crate::swap::create_submarine_swap_script; -use crate::{parse_invoice, Config, LNInvoice, PaymentResponse, RouteHint}; +use crate::{ + parse_invoice, Config, LNInvoice, PaymentResponse, PrepareWithdrawRequest, RouteHint, + SweepRequest, +}; use crate::{OpeningFeeParams, OpeningFeeParamsMenu}; -use crate::{ReceivePaymentRequest, SwapInfo}; +use crate::{PrepareWithdrawResponse, ReceivePaymentRequest, SwapInfo}; pub struct MockBackupTransport { pub num_pushed: std::sync::Mutex, @@ -303,17 +306,20 @@ impl NodeAPI for MockNodeAPI { Ok(()) } - async fn sweep( - &self, - _to_address: String, - _fee_rate_sats_per_vbyte: u64, - ) -> Result { + async fn sweep(&self, _req: SweepRequest) -> Result { Ok(WithdrawResponse { tx: rand_vec_u8(32), txid: rand_vec_u8(32), }) } + async fn prepare_withdraw( + &self, + _req: PrepareWithdrawRequest, + ) -> Result { + Err(anyhow!("Not implemented")) + } + async fn start_signer(&self, _shutdown: mpsc::Receiver<()>) {} async fn list_peers(&self) -> Result> { @@ -358,6 +364,10 @@ impl NodeAPI for MockNodeAPI { fn legacy_derive_bip32_key(&self, _path: Vec) -> Result { Ok(ExtendedPrivKey::new_master(Network::Bitcoin, &[])?) } + + async fn on_chain_balance(&self, _funds: Option) -> Result { + Err(anyhow!("Not implemented")) + } } impl MockNodeAPI { diff --git a/libs/sdk-flutter/ios/Classes/bridge_generated.h b/libs/sdk-flutter/ios/Classes/bridge_generated.h index c5b7f39b3..1dd187452 100644 --- a/libs/sdk-flutter/ios/Classes/bridge_generated.h +++ b/libs/sdk-flutter/ios/Classes/bridge_generated.h @@ -121,6 +121,16 @@ typedef struct wire_BuyBitcoinRequest { struct wire_OpeningFeeParams *opening_fee_params; } wire_BuyBitcoinRequest; +typedef struct wire_SweepRequest { + struct wire_uint_8_list *to_address; + uint64_t sat_per_kw; +} wire_SweepRequest; + +typedef struct wire_PrepareWithdrawRequest { + struct wire_uint_8_list *to_address; + uint64_t fee_rate_sats_per_vbyte; +} wire_PrepareWithdrawRequest; + typedef struct wire_ReverseSwapFeesRequest { uint64_t *send_amount_sat; } wire_ReverseSwapFeesRequest; @@ -223,9 +233,9 @@ void wire_receive_onchain(int64_t port_, struct wire_ReceiveOnchainRequest *req_ void wire_buy_bitcoin(int64_t port_, struct wire_BuyBitcoinRequest *req_data); -void wire_sweep(int64_t port_, - struct wire_uint_8_list *to_address, - uint64_t fee_rate_sats_per_vbyte); +void wire_sweep(int64_t port_, struct wire_SweepRequest *req); + +void wire_prepare_withdraw(int64_t port_, struct wire_PrepareWithdrawRequest *req); void wire_list_refundables(int64_t port_); @@ -268,6 +278,8 @@ struct wire_NodeConfig *new_box_autoadd_node_config_0(void); struct wire_OpeningFeeParams *new_box_autoadd_opening_fee_params_0(void); +struct wire_PrepareWithdrawRequest *new_box_autoadd_prepare_withdraw_request_0(void); + struct wire_ReceiveOnchainRequest *new_box_autoadd_receive_onchain_request_0(void); struct wire_ReceivePaymentRequest *new_box_autoadd_receive_payment_request_0(void); @@ -276,6 +288,8 @@ struct wire_ReverseSwapFeesRequest *new_box_autoadd_reverse_swap_fees_request_0( struct wire_SignMessageRequest *new_box_autoadd_sign_message_request_0(void); +struct wire_SweepRequest *new_box_autoadd_sweep_request_0(void); + uint32_t *new_box_autoadd_u32_0(uint32_t value); uint64_t *new_box_autoadd_u64_0(uint64_t value); @@ -323,6 +337,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) wire_receive_onchain); dummy_var ^= ((int64_t) (void*) wire_buy_bitcoin); dummy_var ^= ((int64_t) (void*) wire_sweep); + dummy_var ^= ((int64_t) (void*) wire_prepare_withdraw); dummy_var ^= ((int64_t) (void*) wire_list_refundables); dummy_var ^= ((int64_t) (void*) wire_refund); dummy_var ^= ((int64_t) (void*) wire_in_progress_swap); @@ -342,10 +357,12 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) new_box_autoadd_ln_url_withdraw_request_data_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_node_config_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_opening_fee_params_0); + dummy_var ^= ((int64_t) (void*) new_box_autoadd_prepare_withdraw_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); dummy_var ^= ((int64_t) (void*) new_box_autoadd_reverse_swap_fees_request_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_sign_message_request_0); + dummy_var ^= ((int64_t) (void*) new_box_autoadd_sweep_request_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_u32_0); dummy_var ^= ((int64_t) (void*) new_box_autoadd_u64_0); dummy_var ^= ((int64_t) (void*) new_uint_8_list_0); diff --git a/libs/sdk-flutter/lib/breez_bridge.dart b/libs/sdk-flutter/lib/breez_bridge.dart index 3a634fa7b..afdd6ac01 100644 --- a/libs/sdk-flutter/lib/breez_bridge.dart +++ b/libs/sdk-flutter/lib/breez_bridge.dart @@ -350,12 +350,10 @@ class BreezSDK { /// Withdraw on-chain funds in the wallet to an external btc address Future sweep({ - required String toAddress, - required int feeRateSatsPerVbyte, + required SweepRequest req, }) async { await _lnToolkit.sweep( - toAddress: toAddress, - feeRateSatsPerVbyte: feeRateSatsPerVbyte, + req: req, ); await listPayments(); } @@ -394,6 +392,17 @@ class BreezSDK { /// Fetches the current recommended fees Future recommendedFees() => _lnToolkit.recommendedFees(); + Future prepareWithdraw({ + required String address, + required int feeRateSatsPerVbyte, + }) async => + _lnToolkit.prepareWithdraw( + req: PrepareWithdrawRequest( + toAddress: address, + feeRateSatsPerVbyte: feeRateSatsPerVbyte, + ), + ); + /* CLI API's */ /// Execute a command directly on the NodeAPI interface. diff --git a/libs/sdk-flutter/lib/bridge_generated.dart b/libs/sdk-flutter/lib/bridge_generated.dart index 595cae16d..b1b1f0e36 100644 --- a/libs/sdk-flutter/lib/bridge_generated.dart +++ b/libs/sdk-flutter/lib/bridge_generated.dart @@ -198,10 +198,15 @@ abstract class BreezSdkCore { FlutterRustBridgeTaskConstMeta get kBuyBitcoinConstMeta; /// See [BreezServices::sweep] - Future sweep({required String toAddress, required int feeRateSatsPerVbyte, dynamic hint}); + Future sweep({required SweepRequest req, dynamic hint}); FlutterRustBridgeTaskConstMeta get kSweepConstMeta; + /// See [BreezServices::prepare_withdraw] + Future prepareWithdraw({required PrepareWithdrawRequest req, dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kPrepareWithdrawConstMeta; + /// See [BreezServices::list_refundables] Future> listRefundables({dynamic hint}); @@ -989,6 +994,31 @@ enum PaymentTypeFilter { All, } +/// We need to prepare a withdraw transaction to know what fee will be charged in satoshis this +/// model holds the request data which consists of the address to withdraw to and the fee rate in +/// satoshis per vbyte which will be converted to absolute satoshis. +class PrepareWithdrawRequest { + final String toAddress; + final int feeRateSatsPerVbyte; + + const PrepareWithdrawRequest({ + required this.toAddress, + required this.feeRateSatsPerVbyte, + }); +} + +/// We need to prepare a withdraw transaction to know what a fee it will be charged in satoshis +/// this model holds the response data, which consists of the weight and the absolute fee in sats +class PrepareWithdrawResponse { + final int weight; + final int feeSat; + + const PrepareWithdrawResponse({ + required this.weight, + required this.feeSat, + }); +} + /// Denominator in an exchange rate class Rate { final String coin; @@ -1308,6 +1338,17 @@ enum SwapStatus { Expired, } +/// Sweep options: The bitcoin address aimed to receive and the fee to be paid +class SweepRequest { + final String toAddress; + final int satPerKw; + + const SweepRequest({ + required this.toAddress, + required this.satPerKw, + }); +} + /// Settings for the symbol representation of a currency class Symbol { final String? grapheme; @@ -1918,21 +1959,36 @@ class BreezSdkCoreImpl implements BreezSdkCore { argNames: ["reqData"], ); - Future sweep({required String toAddress, required int feeRateSatsPerVbyte, dynamic hint}) { - var arg0 = _platform.api2wire_String(toAddress); - var arg1 = _platform.api2wire_u64(feeRateSatsPerVbyte); + Future sweep({required SweepRequest req, dynamic hint}) { + var arg0 = _platform.api2wire_box_autoadd_sweep_request(req); return _platform.executeNormal(FlutterRustBridgeTask( - callFfi: (port_) => _platform.inner.wire_sweep(port_, arg0, arg1), + callFfi: (port_) => _platform.inner.wire_sweep(port_, arg0), parseSuccessData: _wire2api_unit, constMeta: kSweepConstMeta, - argValues: [toAddress, feeRateSatsPerVbyte], + argValues: [req], hint: hint, )); } FlutterRustBridgeTaskConstMeta get kSweepConstMeta => const FlutterRustBridgeTaskConstMeta( debugName: "sweep", - argNames: ["toAddress", "feeRateSatsPerVbyte"], + argNames: ["req"], + ); + + Future prepareWithdraw({required PrepareWithdrawRequest req, dynamic hint}) { + var arg0 = _platform.api2wire_box_autoadd_prepare_withdraw_request(req); + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_prepare_withdraw(port_, arg0), + parseSuccessData: _wire2api_prepare_withdraw_response, + constMeta: kPrepareWithdrawConstMeta, + argValues: [req], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPrepareWithdrawConstMeta => const FlutterRustBridgeTaskConstMeta( + debugName: "prepare_withdraw", + argNames: ["req"], ); Future> listRefundables({dynamic hint}) { @@ -2747,6 +2803,15 @@ class BreezSdkCoreImpl implements BreezSdkCore { return PaymentType.values[raw as int]; } + PrepareWithdrawResponse _wire2api_prepare_withdraw_response(dynamic raw) { + final arr = raw as List; + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return PrepareWithdrawResponse( + weight: _wire2api_u64(arr[0]), + feeSat: _wire2api_u64(arr[1]), + ); + } + Rate _wire2api_rate(dynamic raw) { final arr = raw as List; if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); @@ -3095,6 +3160,14 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { return ptr; } + @protected + ffi.Pointer api2wire_box_autoadd_prepare_withdraw_request( + PrepareWithdrawRequest raw) { + final ptr = inner.new_box_autoadd_prepare_withdraw_request_0(); + _api_fill_to_wire_prepare_withdraw_request(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer api2wire_box_autoadd_receive_onchain_request( ReceiveOnchainRequest raw) { @@ -3126,6 +3199,13 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { return ptr; } + @protected + ffi.Pointer api2wire_box_autoadd_sweep_request(SweepRequest raw) { + final ptr = inner.new_box_autoadd_sweep_request_0(); + _api_fill_to_wire_sweep_request(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer api2wire_box_autoadd_u32(int raw) { return inner.new_box_autoadd_u32_0(api2wire_u32(raw)); @@ -3245,6 +3325,11 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { _api_fill_to_wire_opening_fee_params(apiObj, wireObj.ref); } + void _api_fill_to_wire_box_autoadd_prepare_withdraw_request( + PrepareWithdrawRequest apiObj, ffi.Pointer wireObj) { + _api_fill_to_wire_prepare_withdraw_request(apiObj, wireObj.ref); + } + void _api_fill_to_wire_box_autoadd_receive_onchain_request( ReceiveOnchainRequest apiObj, ffi.Pointer wireObj) { _api_fill_to_wire_receive_onchain_request(apiObj, wireObj.ref); @@ -3265,6 +3350,11 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { _api_fill_to_wire_sign_message_request(apiObj, wireObj.ref); } + void _api_fill_to_wire_box_autoadd_sweep_request( + SweepRequest apiObj, ffi.Pointer wireObj) { + _api_fill_to_wire_sweep_request(apiObj, wireObj.ref); + } + void _api_fill_to_wire_buy_bitcoin_request(BuyBitcoinRequest apiObj, wire_BuyBitcoinRequest wireObj) { wireObj.provider = api2wire_buy_bitcoin_provider(apiObj.provider); wireObj.opening_fee_params = api2wire_opt_box_autoadd_opening_fee_params(apiObj.openingFeeParams); @@ -3358,6 +3448,12 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { if (apiObj != null) _api_fill_to_wire_box_autoadd_opening_fee_params(apiObj, wireObj); } + void _api_fill_to_wire_prepare_withdraw_request( + PrepareWithdrawRequest apiObj, wire_PrepareWithdrawRequest wireObj) { + wireObj.to_address = api2wire_String(apiObj.toAddress); + wireObj.fee_rate_sats_per_vbyte = api2wire_u64(apiObj.feeRateSatsPerVbyte); + } + void _api_fill_to_wire_receive_onchain_request( ReceiveOnchainRequest apiObj, wire_ReceiveOnchainRequest wireObj) { wireObj.opening_fee_params = api2wire_opt_box_autoadd_opening_fee_params(apiObj.openingFeeParams); @@ -3382,6 +3478,11 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { void _api_fill_to_wire_sign_message_request(SignMessageRequest apiObj, wire_SignMessageRequest wireObj) { wireObj.message = api2wire_String(apiObj.message); } + + void _api_fill_to_wire_sweep_request(SweepRequest apiObj, wire_SweepRequest wireObj) { + wireObj.to_address = api2wire_String(apiObj.toAddress); + wireObj.sat_per_kw = api2wire_u64(apiObj.satPerKw); + } } // ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names @@ -3987,21 +4088,33 @@ class BreezSdkCoreWire implements FlutterRustBridgeWireBase { void wire_sweep( int port_, - ffi.Pointer to_address, - int fee_rate_sats_per_vbyte, + ffi.Pointer req, ) { return _wire_sweep( port_, - to_address, - fee_rate_sats_per_vbyte, + req, ); } late final _wire_sweepPtr = - _lookup, ffi.Uint64)>>( - 'wire_sweep'); - late final _wire_sweep = - _wire_sweepPtr.asFunction, int)>(); + _lookup)>>('wire_sweep'); + late final _wire_sweep = _wire_sweepPtr.asFunction)>(); + + void wire_prepare_withdraw( + int port_, + ffi.Pointer req, + ) { + return _wire_prepare_withdraw( + port_, + req, + ); + } + + late final _wire_prepare_withdrawPtr = + _lookup)>>( + 'wire_prepare_withdraw'); + late final _wire_prepare_withdraw = + _wire_prepare_withdrawPtr.asFunction)>(); void wire_list_refundables( int port_, @@ -4230,6 +4343,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_withdraw_request_0() { + return _new_box_autoadd_prepare_withdraw_request_0(); + } + + late final _new_box_autoadd_prepare_withdraw_request_0Ptr = + _lookup Function()>>( + 'new_box_autoadd_prepare_withdraw_request_0'); + late final _new_box_autoadd_prepare_withdraw_request_0 = _new_box_autoadd_prepare_withdraw_request_0Ptr + .asFunction Function()>(); + ffi.Pointer new_box_autoadd_receive_onchain_request_0() { return _new_box_autoadd_receive_onchain_request_0(); } @@ -4270,6 +4393,16 @@ class BreezSdkCoreWire implements FlutterRustBridgeWireBase { late final _new_box_autoadd_sign_message_request_0 = _new_box_autoadd_sign_message_request_0Ptr .asFunction Function()>(); + ffi.Pointer new_box_autoadd_sweep_request_0() { + return _new_box_autoadd_sweep_request_0(); + } + + late final _new_box_autoadd_sweep_request_0Ptr = + _lookup Function()>>( + 'new_box_autoadd_sweep_request_0'); + late final _new_box_autoadd_sweep_request_0 = + _new_box_autoadd_sweep_request_0Ptr.asFunction Function()>(); + ffi.Pointer new_box_autoadd_u32_0( int value, ) { @@ -4494,6 +4627,20 @@ class wire_BuyBitcoinRequest extends ffi.Struct { external ffi.Pointer opening_fee_params; } +class wire_SweepRequest extends ffi.Struct { + external ffi.Pointer to_address; + + @ffi.Uint64() + external int sat_per_kw; +} + +class wire_PrepareWithdrawRequest extends ffi.Struct { + external ffi.Pointer to_address; + + @ffi.Uint64() + external int fee_rate_sats_per_vbyte; +} + class wire_ReverseSwapFeesRequest extends ffi.Struct { external ffi.Pointer send_amount_sat; } diff --git a/tools/sdk-cli/Cargo.lock b/tools/sdk-cli/Cargo.lock index 711055628..662eed0f2 100644 --- a/tools/sdk-cli/Cargo.lock +++ b/tools/sdk-cli/Cargo.lock @@ -443,6 +443,7 @@ dependencies = [ "ecies", "env_logger", "flutter_rust_bridge", + "futures", "gl-client", "hex", "lazy_static", @@ -2313,9 +2314,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -2324,9 +2337,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" diff --git a/tools/sdk-cli/src/command_handlers.rs b/tools/sdk-cli/src/command_handlers.rs index 3d9703dbf..c5cf4aa58 100644 --- a/tools/sdk-cli/src/command_handlers.rs +++ b/tools/sdk-cli/src/command_handlers.rs @@ -5,8 +5,8 @@ use anyhow::{anyhow, Error, Result}; use breez_sdk_core::InputType::{LnUrlAuth, LnUrlPay, LnUrlWithdraw}; use breez_sdk_core::{ parse, BreezEvent, BreezServices, BuyBitcoinRequest, CheckMessageRequest, EventListener, - GreenlightCredentials, PaymentTypeFilter, ReceiveOnchainRequest, ReceivePaymentRequest, - ReverseSwapFeesRequest, SignMessageRequest, + GreenlightCredentials, PaymentTypeFilter, PrepareWithdrawRequest, ReceiveOnchainRequest, + ReceivePaymentRequest, ReverseSwapFeesRequest, SignMessageRequest, SweepRequest, }; use breez_sdk_core::{Config, GreenlightNodeConfig, NodeConfig}; use once_cell::sync::OnceCell; @@ -94,7 +94,7 @@ pub(crate) async fn handle_command( } Commands::Sync {} => { sdk()?.sync().await?; - Ok("Sync finished succesfully".to_string()) + Ok("Sync finished successfully".to_string()) } Commands::Parse { input } => parse(&input) .await @@ -181,10 +181,30 @@ pub(crate) async fn handle_command( } Commands::Sweep { to_address, - sat_per_vbyte: sat_per_byte, + sat_per_kw, + } => { + sdk()? + .sweep(SweepRequest { + to_address, + sat_per_kw, + }) + .await?; + Ok("Onchain funds were swept successfully".to_string()) + } + Commands::PrepareWithdraw { + to_address, + fee_rate_sats_per_vbyte, } => { - sdk()?.sweep(to_address, sat_per_byte).await?; - Ok("Onchain funds were swept succesfully".to_string()) + let response = sdk()? + .prepare_withdraw(PrepareWithdrawRequest { + to_address, + fee_rate_sats_per_vbyte, + }) + .await?; + Ok(format!( + "PrepareWithdraw ran successfully with response: {:?}", + response + )) } Commands::ListLsps {} => { let lsps = sdk()?.list_lsps().await?; @@ -192,7 +212,7 @@ pub(crate) async fn handle_command( } Commands::ConnectLSP { lsp_id } => { sdk()?.connect_lsp(lsp_id).await?; - Ok("LSP connected succesfully".to_string()) + Ok("LSP connected successfully".to_string()) } Commands::OpenChannelFee { amount_msat, diff --git a/tools/sdk-cli/src/commands.rs b/tools/sdk-cli/src/commands.rs index 7bb6c84fa..78ceba9a3 100644 --- a/tools/sdk-cli/src/commands.rs +++ b/tools/sdk-cli/src/commands.rs @@ -119,7 +119,16 @@ pub(crate) enum Commands { to_address: String, /// The fee rate for the sweep transaction - sat_per_vbyte: u64, + sat_per_kw: u64, + }, + + /// Calculate the fee (in sats) for a potential transaction + PrepareWithdraw { + /// The destination address + to_address: String, + + /// The fee rate for the transaction in vbyte/sats + fee_rate_sats_per_vbyte: u64, }, /// List available LSPs