1use anyhow::{anyhow, bail, Result};
2use bitcoin::{bip32, ScriptBuf};
3use boltz_client::{
4 boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2},
5 network::{BitcoinChain, Chain, LiquidChain},
6 swaps::boltz::{
7 CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
8 },
9 BtcSwapScript, Keypair, LBtcSwapScript,
10};
11use derivative::Derivative;
12use elements::AssetId;
13use lwk_wollet::ElementsNetwork;
14use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
15use rusqlite::ToSql;
16use sdk_common::prelude::*;
17use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
18use serde::{Deserialize, Serialize};
19use std::path::PathBuf;
20use std::str::FromStr;
21use std::{cmp::PartialEq, sync::Arc};
22use strum_macros::{Display, EnumString};
23use tokio_with_wasm::alias as tokio;
24
25use crate::{
26 bitcoin,
27 chain::{
28 bitcoin::{esplora::EsploraBitcoinChainService, BitcoinChainService},
29 liquid::{esplora::EsploraLiquidChainService, LiquidChainService},
30 },
31 elements,
32 error::{PaymentError, SdkError, SdkResult},
33 persist::model::PaymentTxBalance,
34 prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
35 receive_swap::DEFAULT_ZERO_CONF_MAX_SAT,
36 side_swap::api::{SIDESWAP_MAINNET_URL, SIDESWAP_TESTNET_URL},
37 utils,
38};
39
40pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
42pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
43pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
44pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api";
45pub const BREEZ_SWAP_PROXY_URL: &str = "https://swap.breez.technology/v2";
46pub const DEFAULT_ONCHAIN_FEE_RATE_LEEWAY_SAT: u64 = 500;
47
48const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
49
50#[derive(Clone, Debug, Serialize)]
51pub enum BlockchainExplorer {
52 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
53 Electrum { url: String },
54 Esplora {
55 url: String,
56 use_waterfalls: bool,
58 },
59}
60
61#[derive(Clone, Debug, Serialize)]
63pub struct Config {
64 pub liquid_explorer: BlockchainExplorer,
65 pub bitcoin_explorer: BlockchainExplorer,
66 pub working_dir: String,
70 pub network: LiquidNetwork,
71 pub payment_timeout_sec: u64,
73 pub sync_service_url: Option<String>,
76 pub zero_conf_max_amount_sat: Option<u64>,
79 pub breez_api_key: Option<String>,
81 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
85 pub use_default_external_input_parsers: bool,
89 pub onchain_fee_rate_leeway_sat: Option<u64>,
96 pub asset_metadata: Option<Vec<AssetMetadata>>,
101 pub sideswap_api_key: Option<String>,
103 pub use_magic_routing_hints: bool,
105}
106
107impl Config {
108 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
109 pub fn mainnet(breez_api_key: Option<String>) -> Self {
110 Config {
111 liquid_explorer: BlockchainExplorer::Electrum {
112 url: "elements-mainnet.breez.technology:50002".to_string(),
113 },
114 bitcoin_explorer: BlockchainExplorer::Electrum {
115 url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
116 },
117 working_dir: ".".to_string(),
118 network: LiquidNetwork::Mainnet,
119 payment_timeout_sec: 15,
120 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
121 zero_conf_max_amount_sat: None,
122 breez_api_key,
123 external_input_parsers: None,
124 use_default_external_input_parsers: true,
125 onchain_fee_rate_leeway_sat: None,
126 asset_metadata: None,
127 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
128 use_magic_routing_hints: true,
129 }
130 }
131
132 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
133 Config {
134 liquid_explorer: BlockchainExplorer::Esplora {
135 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
136 use_waterfalls: true,
137 },
138 bitcoin_explorer: BlockchainExplorer::Esplora {
139 url: "https://blockstream.info/api/".to_string(),
140 use_waterfalls: false,
141 },
142 working_dir: ".".to_string(),
143 network: LiquidNetwork::Mainnet,
144 payment_timeout_sec: 15,
145 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
146 zero_conf_max_amount_sat: None,
147 breez_api_key,
148 external_input_parsers: None,
149 use_default_external_input_parsers: true,
150 onchain_fee_rate_leeway_sat: None,
151 asset_metadata: None,
152 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
153 use_magic_routing_hints: true,
154 }
155 }
156
157 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
158 pub fn testnet(breez_api_key: Option<String>) -> Self {
159 Config {
160 liquid_explorer: BlockchainExplorer::Electrum {
161 url: "elements-testnet.blockstream.info:50002".to_string(),
162 },
163 bitcoin_explorer: BlockchainExplorer::Electrum {
164 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
165 },
166 working_dir: ".".to_string(),
167 network: LiquidNetwork::Testnet,
168 payment_timeout_sec: 15,
169 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
170 zero_conf_max_amount_sat: None,
171 breez_api_key,
172 external_input_parsers: None,
173 use_default_external_input_parsers: true,
174 onchain_fee_rate_leeway_sat: None,
175 asset_metadata: None,
176 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
177 use_magic_routing_hints: true,
178 }
179 }
180
181 pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
182 Config {
183 liquid_explorer: BlockchainExplorer::Esplora {
184 url: "https://blockstream.info/liquidtestnet/api".to_string(),
185 use_waterfalls: false,
186 },
187 bitcoin_explorer: BlockchainExplorer::Esplora {
188 url: "https://blockstream.info/testnet/api/".to_string(),
189 use_waterfalls: false,
190 },
191 working_dir: ".".to_string(),
192 network: LiquidNetwork::Testnet,
193 payment_timeout_sec: 15,
194 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
195 zero_conf_max_amount_sat: None,
196 breez_api_key,
197 external_input_parsers: None,
198 use_default_external_input_parsers: true,
199 onchain_fee_rate_leeway_sat: None,
200 asset_metadata: None,
201 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
202 use_magic_routing_hints: true,
203 }
204 }
205
206 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
207 pub fn regtest() -> Self {
208 Config {
209 liquid_explorer: BlockchainExplorer::Electrum {
210 url: "localhost:19002".to_string(),
211 },
212 bitcoin_explorer: BlockchainExplorer::Electrum {
213 url: "localhost:19001".to_string(),
214 },
215 working_dir: ".".to_string(),
216 network: LiquidNetwork::Regtest,
217 payment_timeout_sec: 15,
218 sync_service_url: Some("http://localhost:8088".to_string()),
219 zero_conf_max_amount_sat: None,
220 breez_api_key: None,
221 external_input_parsers: None,
222 use_default_external_input_parsers: true,
223 onchain_fee_rate_leeway_sat: None,
224 asset_metadata: None,
225 sideswap_api_key: None,
226 use_magic_routing_hints: true,
227 }
228 }
229
230 pub fn regtest_esplora() -> Self {
231 Config {
232 liquid_explorer: BlockchainExplorer::Esplora {
233 url: "http://localhost:3120/api".to_string(),
234 use_waterfalls: true,
235 },
236 bitcoin_explorer: BlockchainExplorer::Esplora {
237 url: "http://localhost:4002/api".to_string(),
238 use_waterfalls: false,
239 },
240 working_dir: ".".to_string(),
241 network: LiquidNetwork::Regtest,
242 payment_timeout_sec: 15,
243 sync_service_url: Some("http://localhost:8089".to_string()),
244 zero_conf_max_amount_sat: None,
245 breez_api_key: None,
246 external_input_parsers: None,
247 use_default_external_input_parsers: true,
248 onchain_fee_rate_leeway_sat: None,
249 asset_metadata: None,
250 sideswap_api_key: None,
251 use_magic_routing_hints: true,
252 }
253 }
254
255 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
256 Ok(PathBuf::from(base_dir)
257 .join(match self.network {
258 LiquidNetwork::Mainnet => "mainnet",
259 LiquidNetwork::Testnet => "testnet",
260 LiquidNetwork::Regtest => "regtest",
261 })
262 .join(fingerprint_hex)
263 .to_str()
264 .ok_or(anyhow::anyhow!(
265 "Could not get retrieve current wallet directory"
266 ))?
267 .to_string())
268 }
269
270 pub fn zero_conf_max_amount_sat(&self) -> u64 {
271 self.zero_conf_max_amount_sat
272 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
273 }
274
275 pub(crate) fn lbtc_asset_id(&self) -> String {
276 utils::lbtc_asset_id(self.network).to_string()
277 }
278
279 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
280 let mut external_input_parsers = Vec::new();
281 if self.use_default_external_input_parsers {
282 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
283 .iter()
284 .map(|(id, regex, url)| ExternalInputParser {
285 provider_id: id.to_string(),
286 input_regex: regex.to_string(),
287 parser_url: url.to_string(),
288 })
289 .collect::<Vec<_>>();
290 external_input_parsers.extend(default_parsers);
291 }
292 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
293
294 external_input_parsers
295 }
296
297 pub(crate) fn default_boltz_url(&self) -> &str {
298 match self.network {
299 LiquidNetwork::Mainnet => {
300 if self.breez_api_key.is_some() {
301 BREEZ_SWAP_PROXY_URL
302 } else {
303 BOLTZ_MAINNET_URL_V2
304 }
305 }
306 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
307 LiquidNetwork::Regtest => "http://localhost:8387/v2",
309 }
310 }
311
312 pub fn sync_enabled(&self) -> bool {
313 self.sync_service_url.is_some()
314 }
315
316 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
317 match self.bitcoin_explorer {
318 BlockchainExplorer::Esplora { .. } => {
319 Arc::new(EsploraBitcoinChainService::new(self.clone()))
320 }
321 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
322 BlockchainExplorer::Electrum { .. } => Arc::new(
323 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
324 ),
325 }
326 }
327
328 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
329 match &self.liquid_explorer {
330 BlockchainExplorer::Esplora { url, .. } => {
331 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
332 bail!("Cannot start the Breez Esplora chain service without providing an API key. See https://sdk-doc-liquid.breez.technology/guide/getting_started.html#api-key")
333 }
334 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
335 }
336 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
337 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
338 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
339 )),
340 }
341 }
342
343 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
344 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
345 match self.network {
346 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
347 LiquidNetwork::Regtest => (false, false),
348 }
349 }
350
351 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
352 pub(crate) fn electrum_client(
353 &self,
354 url: &str,
355 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
356 let (tls, validate_domain) = self.electrum_tls_options();
357 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
358 lwk_wollet::ElectrumClient::with_options(
359 &electrum_url,
360 lwk_wollet::ElectrumOptions { timeout: Some(3) },
361 )
362 }
363
364 pub(crate) fn sideswap_url(&self) -> &'static str {
365 match self.network {
366 LiquidNetwork::Mainnet => SIDESWAP_MAINNET_URL,
367 LiquidNetwork::Testnet => SIDESWAP_TESTNET_URL,
368 LiquidNetwork::Regtest => unimplemented!(),
369 }
370 }
371}
372
373#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
376pub enum LiquidNetwork {
377 Mainnet,
379 Testnet,
381 Regtest,
383}
384impl LiquidNetwork {
385 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
386 match self {
387 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
388 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
389 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
390 }
391 }
392}
393
394impl From<LiquidNetwork> for ElementsNetwork {
395 fn from(value: LiquidNetwork) -> Self {
396 match value {
397 LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
398 LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
399 LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
400 policy_asset: AssetId::from_str(
401 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
402 )
403 .unwrap(),
404 },
405 }
406 }
407}
408
409impl From<LiquidNetwork> for Chain {
410 fn from(value: LiquidNetwork) -> Self {
411 Chain::Liquid(value.into())
412 }
413}
414
415impl From<LiquidNetwork> for LiquidChain {
416 fn from(value: LiquidNetwork) -> Self {
417 match value {
418 LiquidNetwork::Mainnet => LiquidChain::Liquid,
419 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
420 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
421 }
422 }
423}
424
425impl TryFrom<&str> for LiquidNetwork {
426 type Error = anyhow::Error;
427
428 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
429 match value.to_lowercase().as_str() {
430 "mainnet" => Ok(LiquidNetwork::Mainnet),
431 "testnet" => Ok(LiquidNetwork::Testnet),
432 "regtest" => Ok(LiquidNetwork::Regtest),
433 _ => Err(anyhow!("Invalid network")),
434 }
435 }
436}
437
438impl From<LiquidNetwork> for Network {
439 fn from(value: LiquidNetwork) -> Self {
440 match value {
441 LiquidNetwork::Mainnet => Self::Bitcoin,
442 LiquidNetwork::Testnet => Self::Testnet,
443 LiquidNetwork::Regtest => Self::Regtest,
444 }
445 }
446}
447
448impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
449 fn from(value: LiquidNetwork) -> Self {
450 match value {
451 LiquidNetwork::Mainnet => Self::Bitcoin,
452 LiquidNetwork::Testnet => Self::Testnet,
453 LiquidNetwork::Regtest => Self::Regtest,
454 }
455 }
456}
457
458impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
459 fn from(value: LiquidNetwork) -> Self {
460 match value {
461 LiquidNetwork::Mainnet => Self::Bitcoin,
462 LiquidNetwork::Testnet => Self::Testnet,
463 LiquidNetwork::Regtest => Self::Regtest,
464 }
465 }
466}
467
468pub trait EventListener: Send + Sync {
470 fn on_event(&self, e: SdkEvent);
471}
472
473#[derive(Clone, Debug, PartialEq)]
476pub enum SdkEvent {
477 PaymentFailed {
478 details: Payment,
479 },
480 PaymentPending {
481 details: Payment,
482 },
483 PaymentRefundable {
484 details: Payment,
485 },
486 PaymentRefunded {
487 details: Payment,
488 },
489 PaymentRefundPending {
490 details: Payment,
491 },
492 PaymentSucceeded {
493 details: Payment,
494 },
495 PaymentWaitingConfirmation {
496 details: Payment,
497 },
498 PaymentWaitingFeeAcceptance {
499 details: Payment,
500 },
501 Synced,
503 SyncFailed {
505 error: String,
506 },
507 DataSynced {
509 did_pull_new_records: bool,
511 },
512}
513
514#[derive(thiserror::Error, Debug)]
515pub enum SignerError {
516 #[error("Signer error: {err}")]
517 Generic { err: String },
518}
519
520impl From<anyhow::Error> for SignerError {
521 fn from(err: anyhow::Error) -> Self {
522 SignerError::Generic {
523 err: err.to_string(),
524 }
525 }
526}
527
528impl From<bip32::Error> for SignerError {
529 fn from(err: bip32::Error) -> Self {
530 SignerError::Generic {
531 err: err.to_string(),
532 }
533 }
534}
535
536pub trait Signer: Send + Sync {
539 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
542
543 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
549
550 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
552
553 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
555
556 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
558
559 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
562
563 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
565
566 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
568}
569
570pub struct ConnectRequest {
573 pub config: Config,
575 pub mnemonic: Option<String>,
577 pub passphrase: Option<String>,
579 pub seed: Option<Vec<u8>>,
581}
582
583pub struct ConnectWithSignerRequest {
584 pub config: Config,
585}
586
587#[derive(Clone, Debug)]
590pub(crate) struct ReservedAddress {
591 pub(crate) address: String,
593 pub(crate) expiry_block_height: u32,
595}
596
597#[derive(Clone, Debug, Serialize)]
599pub enum PaymentMethod {
600 #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
601 Lightning,
602 Bolt11Invoice,
603 Bolt12Offer,
604 BitcoinAddress,
605 LiquidAddress,
606}
607
608#[derive(Debug, Serialize, Clone)]
609pub enum ReceiveAmount {
610 Bitcoin { payer_amount_sat: u64 },
612
613 Asset {
615 asset_id: String,
616 payer_amount: Option<f64>,
617 },
618}
619
620#[derive(Debug, Serialize)]
622pub struct PrepareReceiveRequest {
623 pub payment_method: PaymentMethod,
624 pub amount: Option<ReceiveAmount>,
626}
627
628#[derive(Debug, Serialize, Clone)]
630pub struct PrepareReceiveResponse {
631 pub payment_method: PaymentMethod,
632 pub fees_sat: u64,
641 pub amount: Option<ReceiveAmount>,
643 pub min_payer_amount_sat: Option<u64>,
647 pub max_payer_amount_sat: Option<u64>,
651 pub swapper_feerate: Option<f64>,
655}
656
657#[derive(Debug, Serialize)]
659pub struct ReceivePaymentRequest {
660 pub prepare_response: PrepareReceiveResponse,
661 pub description: Option<String>,
663 pub use_description_hash: Option<bool>,
665 pub payer_note: Option<String>,
667}
668
669#[derive(Debug, Serialize)]
671pub struct ReceivePaymentResponse {
672 pub destination: String,
675 pub liquid_expiration_blockheight: Option<u32>,
677 pub bitcoin_expiration_blockheight: Option<u32>,
679}
680
681#[derive(Debug, Serialize)]
683pub struct CreateBolt12InvoiceRequest {
684 pub offer: String,
686 pub invoice_request: String,
688}
689
690#[derive(Debug, Serialize, Clone)]
692pub struct CreateBolt12InvoiceResponse {
693 pub invoice: String,
695}
696
697#[derive(Debug, Serialize)]
699pub struct Limits {
700 pub min_sat: u64,
701 pub max_sat: u64,
702 pub max_zero_conf_sat: u64,
703}
704
705#[derive(Debug, Serialize)]
707pub struct LightningPaymentLimitsResponse {
708 pub send: Limits,
710 pub receive: Limits,
712}
713
714#[derive(Debug, Serialize)]
716pub struct OnchainPaymentLimitsResponse {
717 pub send: Limits,
719 pub receive: Limits,
721}
722
723#[derive(Debug, Serialize, Clone)]
725pub struct PrepareSendRequest {
726 pub destination: String,
729 pub amount: Option<PayAmount>,
732}
733
734#[derive(Clone, Debug, Serialize)]
736pub enum SendDestination {
737 LiquidAddress {
738 address_data: liquid::LiquidAddressData,
739 bip353_address: Option<String>,
741 },
742 Bolt11 {
743 invoice: LNInvoice,
744 bip353_address: Option<String>,
746 },
747 Bolt12 {
748 offer: LNOffer,
749 receiver_amount_sat: u64,
750 bip353_address: Option<String>,
752 },
753}
754
755#[derive(Debug, Serialize, Clone)]
757pub struct PrepareSendResponse {
758 pub destination: SendDestination,
759 pub amount: Option<PayAmount>,
761 pub fees_sat: Option<u64>,
764 pub estimated_asset_fees: Option<f64>,
768 pub exchange_amount_sat: Option<u64>,
771}
772
773#[derive(Debug, Serialize)]
775pub struct SendPaymentRequest {
776 pub prepare_response: PrepareSendResponse,
777 pub use_asset_fees: Option<bool>,
779 pub payer_note: Option<String>,
781}
782
783#[derive(Debug, Serialize)]
785pub struct SendPaymentResponse {
786 pub payment: Payment,
787}
788
789pub(crate) struct SendPaymentViaSwapRequest {
790 pub(crate) invoice: String,
791 pub(crate) bolt12_offer: Option<String>,
792 pub(crate) payment_hash: String,
793 pub(crate) description: Option<String>,
794 pub(crate) receiver_amount_sat: u64,
795 pub(crate) fees_sat: u64,
796}
797
798pub(crate) struct PayLiquidRequest {
799 pub address_data: LiquidAddressData,
800 pub to_asset: String,
801 pub receiver_amount_sat: u64,
802 pub asset_pay_fees: bool,
803 pub fees_sat: Option<u64>,
804}
805
806pub(crate) struct PaySideSwapRequest {
807 pub address_data: LiquidAddressData,
808 pub to_asset: String,
809 pub receiver_amount_sat: u64,
810 pub fees_sat: u64,
811 pub amount: Option<PayAmount>,
812}
813
814#[derive(Debug, Serialize, Clone)]
816pub enum PayAmount {
817 Bitcoin { receiver_amount_sat: u64 },
819
820 Asset {
822 to_asset: String,
824 receiver_amount: f64,
825 estimate_asset_fees: Option<bool>,
826 from_asset: Option<String>,
829 },
830
831 Drain,
833}
834
835impl PayAmount {
836 pub(crate) fn is_sideswap_payment(&self) -> bool {
837 match self {
838 PayAmount::Asset {
839 to_asset,
840 from_asset,
841 ..
842 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
843 _ => false,
844 }
845 }
846}
847
848#[derive(Debug, Serialize, Clone)]
850pub struct PreparePayOnchainRequest {
851 pub amount: PayAmount,
853 pub fee_rate_sat_per_vbyte: Option<u32>,
855}
856
857#[derive(Debug, Serialize, Clone)]
859pub struct PreparePayOnchainResponse {
860 pub receiver_amount_sat: u64,
861 pub claim_fees_sat: u64,
862 pub total_fees_sat: u64,
863}
864
865#[derive(Debug, Serialize)]
867pub struct PayOnchainRequest {
868 pub address: String,
869 pub prepare_response: PreparePayOnchainResponse,
870}
871
872#[derive(Debug, Serialize)]
874pub struct PrepareRefundRequest {
875 pub swap_address: String,
877 pub refund_address: String,
879 pub fee_rate_sat_per_vbyte: u32,
881}
882
883#[derive(Debug, Serialize)]
885pub struct PrepareRefundResponse {
886 pub tx_vsize: u32,
887 pub tx_fee_sat: u64,
888 pub last_refund_tx_id: Option<String>,
890}
891
892#[derive(Debug, Serialize)]
894pub struct RefundRequest {
895 pub swap_address: String,
897 pub refund_address: String,
899 pub fee_rate_sat_per_vbyte: u32,
901}
902
903#[derive(Debug, Serialize)]
905pub struct RefundResponse {
906 pub refund_tx_id: String,
907}
908
909#[derive(Clone, Debug, Default, Serialize, Deserialize)]
911pub struct AssetBalance {
912 pub asset_id: String,
913 pub balance_sat: u64,
914 pub name: Option<String>,
915 pub ticker: Option<String>,
916 pub balance: Option<f64>,
917}
918
919#[derive(Debug, Serialize, Deserialize, Default)]
920pub struct BlockchainInfo {
921 pub liquid_tip: u32,
922 pub bitcoin_tip: u32,
923}
924
925#[derive(Copy, Clone)]
926pub(crate) struct ChainTips {
927 pub liquid_tip: u32,
928 pub bitcoin_tip: Option<u32>,
929}
930
931#[derive(Debug, Serialize, Deserialize)]
932pub struct WalletInfo {
933 pub balance_sat: u64,
935 pub pending_send_sat: u64,
937 pub pending_receive_sat: u64,
939 pub fingerprint: String,
941 pub pubkey: String,
943 #[serde(default)]
945 pub asset_balances: Vec<AssetBalance>,
946}
947
948impl WalletInfo {
949 pub(crate) fn validate_sufficient_funds(
950 &self,
951 network: LiquidNetwork,
952 amount_sat: u64,
953 fees_sat: Option<u64>,
954 asset_id: &str,
955 ) -> Result<(), PaymentError> {
956 let fees_sat = fees_sat.unwrap_or(0);
957 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
958 ensure_sdk!(
959 amount_sat + fees_sat <= self.balance_sat,
960 PaymentError::InsufficientFunds
961 );
962 } else {
963 match self
964 .asset_balances
965 .iter()
966 .find(|ab| ab.asset_id.eq(asset_id))
967 {
968 Some(asset_balance) => ensure_sdk!(
969 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
970 PaymentError::InsufficientFunds
971 ),
972 None => return Err(PaymentError::InsufficientFunds),
973 }
974 }
975 Ok(())
976 }
977}
978
979#[derive(Debug, Serialize, Deserialize)]
981pub struct GetInfoResponse {
982 pub wallet_info: WalletInfo,
984 #[serde(default)]
986 pub blockchain_info: BlockchainInfo,
987}
988
989#[derive(Clone, Debug, PartialEq)]
991pub struct SignMessageRequest {
992 pub message: String,
993}
994
995#[derive(Clone, Debug, PartialEq)]
997pub struct SignMessageResponse {
998 pub signature: String,
999}
1000
1001#[derive(Clone, Debug, PartialEq)]
1003pub struct CheckMessageRequest {
1004 pub message: String,
1006 pub pubkey: String,
1008 pub signature: String,
1010}
1011
1012#[derive(Clone, Debug, PartialEq)]
1014pub struct CheckMessageResponse {
1015 pub is_valid: bool,
1018}
1019
1020#[derive(Debug, Serialize)]
1022pub struct BackupRequest {
1023 pub backup_path: Option<String>,
1030}
1031
1032#[derive(Debug, Serialize)]
1034pub struct RestoreRequest {
1035 pub backup_path: Option<String>,
1036}
1037
1038#[derive(Default)]
1040pub struct ListPaymentsRequest {
1041 pub filters: Option<Vec<PaymentType>>,
1042 pub states: Option<Vec<PaymentState>>,
1043 pub from_timestamp: Option<i64>,
1045 pub to_timestamp: Option<i64>,
1047 pub offset: Option<u32>,
1048 pub limit: Option<u32>,
1049 pub details: Option<ListPaymentDetails>,
1050 pub sort_ascending: Option<bool>,
1051}
1052
1053#[derive(Debug, Serialize)]
1055pub enum ListPaymentDetails {
1056 Liquid {
1058 asset_id: Option<String>,
1060 destination: Option<String>,
1062 },
1063
1064 Bitcoin {
1066 address: Option<String>,
1068 },
1069}
1070
1071#[derive(Debug, Serialize)]
1073pub enum GetPaymentRequest {
1074 PaymentHash { payment_hash: String },
1076 SwapId { swap_id: String },
1078}
1079
1080#[sdk_macros::async_trait]
1082pub(crate) trait BlockListener: Send + Sync {
1083 async fn on_bitcoin_block(&self, height: u32);
1084 async fn on_liquid_block(&self, height: u32);
1085}
1086
1087#[derive(Clone, Debug)]
1089pub enum Swap {
1090 Chain(ChainSwap),
1091 Send(SendSwap),
1092 Receive(ReceiveSwap),
1093}
1094impl Swap {
1095 pub(crate) fn id(&self) -> String {
1096 match &self {
1097 Swap::Chain(ChainSwap { id, .. })
1098 | Swap::Send(SendSwap { id, .. })
1099 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1100 }
1101 }
1102
1103 pub(crate) fn version(&self) -> u64 {
1104 match self {
1105 Swap::Chain(ChainSwap { metadata, .. })
1106 | Swap::Send(SendSwap { metadata, .. })
1107 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1108 }
1109 }
1110
1111 pub(crate) fn set_version(&mut self, version: u64) {
1112 match self {
1113 Swap::Chain(chain_swap) => {
1114 chain_swap.metadata.version = version;
1115 }
1116 Swap::Send(send_swap) => {
1117 send_swap.metadata.version = version;
1118 }
1119 Swap::Receive(receive_swap) => {
1120 receive_swap.metadata.version = version;
1121 }
1122 }
1123 }
1124
1125 pub(crate) fn last_updated_at(&self) -> u32 {
1126 match self {
1127 Swap::Chain(ChainSwap { metadata, .. })
1128 | Swap::Send(SendSwap { metadata, .. })
1129 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1130 }
1131 }
1132}
1133impl From<ChainSwap> for Swap {
1134 fn from(swap: ChainSwap) -> Self {
1135 Self::Chain(swap)
1136 }
1137}
1138impl From<SendSwap> for Swap {
1139 fn from(swap: SendSwap) -> Self {
1140 Self::Send(swap)
1141 }
1142}
1143impl From<ReceiveSwap> for Swap {
1144 fn from(swap: ReceiveSwap) -> Self {
1145 Self::Receive(swap)
1146 }
1147}
1148
1149#[derive(Clone, Debug)]
1150pub(crate) enum SwapScriptV2 {
1151 Bitcoin(BtcSwapScript),
1152 Liquid(LBtcSwapScript),
1153}
1154impl SwapScriptV2 {
1155 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1156 match self {
1157 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1158 _ => Err(anyhow!("Invalid chain")),
1159 }
1160 }
1161
1162 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1163 match self {
1164 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1165 _ => Err(anyhow!("Invalid chain")),
1166 }
1167 }
1168}
1169
1170#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1171pub enum Direction {
1172 Incoming = 0,
1173 Outgoing = 1,
1174}
1175impl ToSql for Direction {
1176 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1177 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1178 }
1179}
1180impl FromSql for Direction {
1181 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1182 match value {
1183 ValueRef::Integer(i) => match i as u8 {
1184 0 => Ok(Direction::Incoming),
1185 1 => Ok(Direction::Outgoing),
1186 _ => Err(FromSqlError::OutOfRange(i)),
1187 },
1188 _ => Err(FromSqlError::InvalidType),
1189 }
1190 }
1191}
1192
1193#[derive(Clone, Debug, Default)]
1194pub(crate) struct SwapMetadata {
1195 pub(crate) version: u64,
1197 pub(crate) last_updated_at: u32,
1198 pub(crate) is_local: bool,
1199}
1200
1201#[derive(Clone, Debug, Derivative)]
1205#[derivative(PartialEq)]
1206pub struct ChainSwap {
1207 pub(crate) id: String,
1208 pub(crate) direction: Direction,
1209 pub(crate) claim_address: Option<String>,
1212 pub(crate) lockup_address: String,
1213 pub(crate) refund_address: Option<String>,
1215 pub(crate) timeout_block_height: u32,
1217 pub(crate) claim_timeout_block_height: u32,
1219 pub(crate) preimage: String,
1220 pub(crate) description: Option<String>,
1221 pub(crate) payer_amount_sat: u64,
1223 pub(crate) actual_payer_amount_sat: Option<u64>,
1226 pub(crate) receiver_amount_sat: u64,
1228 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1230 pub(crate) claim_fees_sat: u64,
1231 pub(crate) pair_fees_json: String,
1233 pub(crate) accept_zero_conf: bool,
1234 pub(crate) create_response_json: String,
1236 pub(crate) server_lockup_tx_id: Option<String>,
1238 pub(crate) user_lockup_tx_id: Option<String>,
1240 pub(crate) claim_tx_id: Option<String>,
1242 pub(crate) refund_tx_id: Option<String>,
1244 pub(crate) created_at: u32,
1245 pub(crate) state: PaymentState,
1246 pub(crate) claim_private_key: String,
1247 pub(crate) refund_private_key: String,
1248 pub(crate) auto_accepted_fees: bool,
1249 #[derivative(PartialEq = "ignore")]
1251 pub(crate) metadata: SwapMetadata,
1252}
1253impl ChainSwap {
1254 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1255 utils::decode_keypair(&self.claim_private_key)
1256 }
1257
1258 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1259 utils::decode_keypair(&self.refund_private_key)
1260 }
1261
1262 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1263 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1264 serde_json::from_str(&self.create_response_json).map_err(|e| {
1265 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1266 })?;
1267
1268 Ok(CreateChainResponse {
1269 id: self.id.clone(),
1270 claim_details: internal_create_response.claim_details,
1271 lockup_details: internal_create_response.lockup_details,
1272 })
1273 }
1274
1275 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1276 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1277 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1278
1279 Ok(pair)
1280 }
1281
1282 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1283 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1284 let our_pubkey = self.get_claim_keypair()?.public_key();
1285 let swap_script = match self.direction {
1286 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1287 Side::Claim,
1288 chain_swap_details,
1289 our_pubkey.into(),
1290 )?),
1291 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1292 Side::Claim,
1293 chain_swap_details,
1294 our_pubkey.into(),
1295 )?),
1296 };
1297 Ok(swap_script)
1298 }
1299
1300 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1301 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1302 let our_pubkey = self.get_refund_keypair()?.public_key();
1303 let swap_script = match self.direction {
1304 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1305 Side::Lockup,
1306 chain_swap_details,
1307 our_pubkey.into(),
1308 )?),
1309 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1310 Side::Lockup,
1311 chain_swap_details,
1312 our_pubkey.into(),
1313 )?),
1314 };
1315 Ok(swap_script)
1316 }
1317
1318 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1320 &self,
1321 network: LiquidNetwork,
1322 ) -> SdkResult<ScriptBuf> {
1323 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1324 let script_pubkey = swap_script
1325 .to_address(network.as_bitcoin_chain())
1326 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1327 .script_pubkey();
1328 Ok(script_pubkey)
1329 }
1330
1331 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1332 RefundableSwap {
1333 swap_address: self.lockup_address.clone(),
1334 timestamp: self.created_at,
1335 amount_sat,
1336 last_refund_tx_id: self.refund_tx_id.clone(),
1337 }
1338 }
1339
1340 pub(crate) fn from_boltz_struct_to_json(
1341 create_response: &CreateChainResponse,
1342 expected_swap_id: &str,
1343 ) -> Result<String, PaymentError> {
1344 let internal_create_response =
1345 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1346 create_response,
1347 expected_swap_id,
1348 )?;
1349
1350 let create_response_json =
1351 serde_json::to_string(&internal_create_response).map_err(|e| {
1352 PaymentError::Generic {
1353 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1354 }
1355 })?;
1356
1357 Ok(create_response_json)
1358 }
1359
1360 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1361 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1362 }
1363}
1364
1365#[derive(Clone, Debug, Default)]
1366pub(crate) struct ChainSwapUpdate {
1367 pub(crate) swap_id: String,
1368 pub(crate) to_state: PaymentState,
1369 pub(crate) server_lockup_tx_id: Option<String>,
1370 pub(crate) user_lockup_tx_id: Option<String>,
1371 pub(crate) claim_address: Option<String>,
1372 pub(crate) claim_tx_id: Option<String>,
1373 pub(crate) refund_tx_id: Option<String>,
1374}
1375
1376#[derive(Clone, Debug, Derivative)]
1378#[derivative(PartialEq)]
1379pub struct SendSwap {
1380 pub(crate) id: String,
1381 pub(crate) invoice: String,
1383 pub(crate) bolt12_offer: Option<String>,
1385 pub(crate) payment_hash: Option<String>,
1386 pub(crate) destination_pubkey: Option<String>,
1387 pub(crate) description: Option<String>,
1388 pub(crate) preimage: Option<String>,
1389 pub(crate) payer_amount_sat: u64,
1390 pub(crate) receiver_amount_sat: u64,
1391 pub(crate) pair_fees_json: String,
1393 pub(crate) create_response_json: String,
1395 pub(crate) lockup_tx_id: Option<String>,
1397 pub(crate) refund_address: Option<String>,
1399 pub(crate) refund_tx_id: Option<String>,
1401 pub(crate) created_at: u32,
1402 pub(crate) timeout_block_height: u64,
1403 pub(crate) state: PaymentState,
1404 pub(crate) refund_private_key: String,
1405 #[derivative(PartialEq = "ignore")]
1407 pub(crate) metadata: SwapMetadata,
1408}
1409impl SendSwap {
1410 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1411 utils::decode_keypair(&self.refund_private_key)
1412 }
1413
1414 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1415 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1416 serde_json::from_str(&self.create_response_json).map_err(|e| {
1417 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1418 })?;
1419
1420 let res = CreateSubmarineResponse {
1421 id: self.id.clone(),
1422 accept_zero_conf: internal_create_response.accept_zero_conf,
1423 address: internal_create_response.address.clone(),
1424 bip21: internal_create_response.bip21.clone(),
1425 claim_public_key: crate::utils::json_to_pubkey(
1426 &internal_create_response.claim_public_key,
1427 )?,
1428 expected_amount: internal_create_response.expected_amount,
1429 referral_id: internal_create_response.referral_id,
1430 swap_tree: internal_create_response.swap_tree.clone().into(),
1431 timeout_block_height: internal_create_response.timeout_block_height,
1432 blinding_key: internal_create_response.blinding_key.clone(),
1433 };
1434 Ok(res)
1435 }
1436
1437 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1438 LBtcSwapScript::submarine_from_swap_resp(
1439 &self.get_boltz_create_response()?,
1440 self.get_refund_keypair()?.public_key().into(),
1441 )
1442 .map_err(|e| {
1443 SdkError::generic(format!(
1444 "Failed to create swap script for Send Swap {}: {e:?}",
1445 self.id
1446 ))
1447 })
1448 }
1449
1450 pub(crate) fn from_boltz_struct_to_json(
1451 create_response: &CreateSubmarineResponse,
1452 expected_swap_id: &str,
1453 ) -> Result<String, PaymentError> {
1454 let internal_create_response =
1455 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1456 create_response,
1457 expected_swap_id,
1458 )?;
1459
1460 let create_response_json =
1461 serde_json::to_string(&internal_create_response).map_err(|e| {
1462 PaymentError::Generic {
1463 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1464 }
1465 })?;
1466
1467 Ok(create_response_json)
1468 }
1469}
1470
1471#[derive(Clone, Debug, Derivative)]
1473#[derivative(PartialEq)]
1474pub struct ReceiveSwap {
1475 pub(crate) id: String,
1476 pub(crate) preimage: String,
1477 pub(crate) create_response_json: String,
1479 pub(crate) claim_private_key: String,
1480 pub(crate) invoice: String,
1481 pub(crate) bolt12_offer: Option<String>,
1483 pub(crate) payment_hash: Option<String>,
1484 pub(crate) destination_pubkey: Option<String>,
1485 pub(crate) description: Option<String>,
1486 pub(crate) payer_note: Option<String>,
1487 pub(crate) payer_amount_sat: u64,
1489 pub(crate) receiver_amount_sat: u64,
1490 pub(crate) pair_fees_json: String,
1492 pub(crate) claim_fees_sat: u64,
1493 pub(crate) claim_address: Option<String>,
1495 pub(crate) claim_tx_id: Option<String>,
1497 pub(crate) lockup_tx_id: Option<String>,
1499 pub(crate) mrh_address: String,
1501 pub(crate) mrh_tx_id: Option<String>,
1503 pub(crate) created_at: u32,
1506 pub(crate) timeout_block_height: u32,
1507 pub(crate) state: PaymentState,
1508 #[derivative(PartialEq = "ignore")]
1510 pub(crate) metadata: SwapMetadata,
1511}
1512impl ReceiveSwap {
1513 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1514 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1515 }
1516
1517 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1518 Ok(self
1519 .get_swap_script()?
1520 .funding_addrs
1521 .ok_or(anyhow!("No funding address found"))?
1522 .script_pubkey())
1523 }
1524
1525 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1526 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1527 serde_json::from_str(&self.create_response_json).map_err(|e| {
1528 PaymentError::Generic {
1529 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1530 }
1531 })?;
1532
1533 let res = CreateReverseResponse {
1534 id: self.id.clone(),
1535 invoice: Some(self.invoice.clone()),
1536 swap_tree: internal_create_response.swap_tree.clone().into(),
1537 lockup_address: internal_create_response.lockup_address.clone(),
1538 refund_public_key: crate::utils::json_to_pubkey(
1539 &internal_create_response.refund_public_key,
1540 )?,
1541 timeout_block_height: internal_create_response.timeout_block_height,
1542 onchain_amount: internal_create_response.onchain_amount,
1543 blinding_key: internal_create_response.blinding_key.clone(),
1544 };
1545 Ok(res)
1546 }
1547
1548 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1549 let keypair = self.get_claim_keypair()?;
1550 let create_response =
1551 self.get_boltz_create_response()
1552 .map_err(|e| PaymentError::Generic {
1553 err: format!(
1554 "Failed to create swap script for Receive Swap {}: {e:?}",
1555 self.id
1556 ),
1557 })?;
1558 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1559 .map_err(|e| PaymentError::Generic {
1560 err: format!(
1561 "Failed to create swap script for Receive Swap {}: {e:?}",
1562 self.id
1563 ),
1564 })
1565 }
1566
1567 pub(crate) fn from_boltz_struct_to_json(
1568 create_response: &CreateReverseResponse,
1569 expected_swap_id: &str,
1570 expected_invoice: Option<&str>,
1571 ) -> Result<String, PaymentError> {
1572 let internal_create_response =
1573 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1574 create_response,
1575 expected_swap_id,
1576 expected_invoice,
1577 )?;
1578
1579 let create_response_json =
1580 serde_json::to_string(&internal_create_response).map_err(|e| {
1581 PaymentError::Generic {
1582 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1583 }
1584 })?;
1585
1586 Ok(create_response_json)
1587 }
1588}
1589
1590#[derive(Clone, Debug, PartialEq, Serialize)]
1592pub struct RefundableSwap {
1593 pub swap_address: String,
1594 pub timestamp: u32,
1595 pub amount_sat: u64,
1597 pub last_refund_tx_id: Option<String>,
1599}
1600
1601#[derive(Clone, Debug, Derivative)]
1603#[derivative(PartialEq)]
1604pub(crate) struct Bolt12Offer {
1605 pub(crate) id: String,
1607 pub(crate) description: String,
1609 pub(crate) private_key: String,
1611 pub(crate) webhook_url: Option<String>,
1613 pub(crate) created_at: u32,
1615}
1616impl Bolt12Offer {
1617 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1618 utils::decode_keypair(&self.private_key)
1619 }
1620}
1621impl TryFrom<Bolt12Offer> for Offer {
1622 type Error = SdkError;
1623
1624 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1625 Offer::from_str(&val.id)
1626 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1627 }
1628}
1629
1630#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1632#[strum(serialize_all = "lowercase")]
1633pub enum PaymentState {
1634 #[default]
1635 Created = 0,
1636
1637 Pending = 1,
1657
1658 Complete = 2,
1670
1671 Failed = 3,
1679
1680 TimedOut = 4,
1685
1686 Refundable = 5,
1691
1692 RefundPending = 6,
1698
1699 WaitingFeeAcceptance = 7,
1711}
1712
1713impl ToSql for PaymentState {
1714 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1715 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1716 }
1717}
1718impl FromSql for PaymentState {
1719 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1720 match value {
1721 ValueRef::Integer(i) => match i as u8 {
1722 0 => Ok(PaymentState::Created),
1723 1 => Ok(PaymentState::Pending),
1724 2 => Ok(PaymentState::Complete),
1725 3 => Ok(PaymentState::Failed),
1726 4 => Ok(PaymentState::TimedOut),
1727 5 => Ok(PaymentState::Refundable),
1728 6 => Ok(PaymentState::RefundPending),
1729 7 => Ok(PaymentState::WaitingFeeAcceptance),
1730 _ => Err(FromSqlError::OutOfRange(i)),
1731 },
1732 _ => Err(FromSqlError::InvalidType),
1733 }
1734 }
1735}
1736
1737impl PaymentState {
1738 pub(crate) fn is_refundable(&self) -> bool {
1739 matches!(
1740 self,
1741 PaymentState::Refundable
1742 | PaymentState::RefundPending
1743 | PaymentState::WaitingFeeAcceptance
1744 )
1745 }
1746}
1747
1748#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1749#[strum(serialize_all = "lowercase")]
1750pub enum PaymentType {
1751 Receive = 0,
1752 Send = 1,
1753}
1754impl From<Direction> for PaymentType {
1755 fn from(value: Direction) -> Self {
1756 match value {
1757 Direction::Incoming => Self::Receive,
1758 Direction::Outgoing => Self::Send,
1759 }
1760 }
1761}
1762impl ToSql for PaymentType {
1763 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1764 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1765 }
1766}
1767impl FromSql for PaymentType {
1768 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1769 match value {
1770 ValueRef::Integer(i) => match i as u8 {
1771 0 => Ok(PaymentType::Receive),
1772 1 => Ok(PaymentType::Send),
1773 _ => Err(FromSqlError::OutOfRange(i)),
1774 },
1775 _ => Err(FromSqlError::InvalidType),
1776 }
1777 }
1778}
1779
1780#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1781pub enum PaymentStatus {
1782 Pending = 0,
1783 Complete = 1,
1784}
1785impl ToSql for PaymentStatus {
1786 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1787 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1788 }
1789}
1790impl FromSql for PaymentStatus {
1791 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1792 match value {
1793 ValueRef::Integer(i) => match i as u8 {
1794 0 => Ok(PaymentStatus::Pending),
1795 1 => Ok(PaymentStatus::Complete),
1796 _ => Err(FromSqlError::OutOfRange(i)),
1797 },
1798 _ => Err(FromSqlError::InvalidType),
1799 }
1800 }
1801}
1802
1803#[derive(Debug, Clone, Serialize)]
1804pub struct PaymentTxData {
1805 pub tx_id: String,
1807
1808 pub timestamp: Option<u32>,
1810
1811 pub fees_sat: u64,
1813
1814 pub is_confirmed: bool,
1816
1817 pub unblinding_data: Option<String>,
1820}
1821
1822#[derive(Debug, Clone, Serialize)]
1823pub enum PaymentSwapType {
1824 Receive,
1825 Send,
1826 Chain,
1827}
1828
1829#[derive(Debug, Clone, Serialize)]
1830pub struct PaymentSwapData {
1831 pub swap_id: String,
1832
1833 pub swap_type: PaymentSwapType,
1834
1835 pub created_at: u32,
1837
1838 pub expiration_blockheight: u32,
1841
1842 pub claim_expiration_blockheight: Option<u32>,
1844
1845 pub preimage: Option<String>,
1846 pub invoice: Option<String>,
1847 pub bolt12_offer: Option<String>,
1848 pub payment_hash: Option<String>,
1849 pub destination_pubkey: Option<String>,
1850 pub description: String,
1851 pub payer_note: Option<String>,
1852
1853 pub payer_amount_sat: u64,
1855
1856 pub receiver_amount_sat: u64,
1858
1859 pub swapper_fees_sat: u64,
1861
1862 pub refund_tx_id: Option<String>,
1863 pub refund_tx_amount_sat: Option<u64>,
1864
1865 pub bitcoin_address: Option<String>,
1868
1869 pub status: PaymentState,
1871}
1872
1873#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1875pub struct LnUrlInfo {
1876 pub ln_address: Option<String>,
1877 pub lnurl_pay_comment: Option<String>,
1878 pub lnurl_pay_domain: Option<String>,
1879 pub lnurl_pay_metadata: Option<String>,
1880 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1881 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1882 pub lnurl_withdraw_endpoint: Option<String>,
1883}
1884
1885#[derive(Debug, Clone, Serialize)]
1889pub struct AssetMetadata {
1890 pub asset_id: String,
1892 pub name: String,
1894 pub ticker: String,
1896 pub precision: u8,
1899 pub fiat_id: Option<String>,
1901}
1902
1903impl AssetMetadata {
1904 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1905 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1906 }
1907
1908 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1909 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1910 }
1911}
1912
1913#[derive(Clone, Debug, PartialEq, Serialize)]
1916pub struct AssetInfo {
1917 pub name: String,
1919 pub ticker: String,
1921 pub amount: f64,
1924 pub fees: Option<f64>,
1927}
1928
1929#[derive(Debug, Clone, PartialEq, Serialize)]
1931#[allow(clippy::large_enum_variant)]
1932pub enum PaymentDetails {
1933 Lightning {
1935 swap_id: String,
1936
1937 description: String,
1939
1940 liquid_expiration_blockheight: u32,
1942
1943 preimage: Option<String>,
1945
1946 invoice: Option<String>,
1950
1951 bolt12_offer: Option<String>,
1952
1953 payment_hash: Option<String>,
1955
1956 destination_pubkey: Option<String>,
1958
1959 lnurl_info: Option<LnUrlInfo>,
1961
1962 bip353_address: Option<String>,
1964
1965 payer_note: Option<String>,
1967
1968 claim_tx_id: Option<String>,
1970
1971 refund_tx_id: Option<String>,
1973
1974 refund_tx_amount_sat: Option<u64>,
1976 },
1977 Liquid {
1979 destination: String,
1981
1982 description: String,
1984
1985 asset_id: String,
1987
1988 asset_info: Option<AssetInfo>,
1990
1991 lnurl_info: Option<LnUrlInfo>,
1993
1994 bip353_address: Option<String>,
1996
1997 payer_note: Option<String>,
1999 },
2000 Bitcoin {
2002 swap_id: String,
2003
2004 bitcoin_address: String,
2006
2007 description: String,
2009
2010 auto_accepted_fees: bool,
2014
2015 liquid_expiration_blockheight: u32,
2017
2018 bitcoin_expiration_blockheight: u32,
2020
2021 lockup_tx_id: Option<String>,
2023
2024 claim_tx_id: Option<String>,
2026
2027 refund_tx_id: Option<String>,
2029
2030 refund_tx_amount_sat: Option<u64>,
2032 },
2033}
2034
2035impl PaymentDetails {
2036 pub(crate) fn get_swap_id(&self) -> Option<String> {
2037 match self {
2038 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2039 Some(swap_id.clone())
2040 }
2041 Self::Liquid { .. } => None,
2042 }
2043 }
2044
2045 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2046 match self {
2047 Self::Lightning {
2048 refund_tx_amount_sat,
2049 ..
2050 }
2051 | Self::Bitcoin {
2052 refund_tx_amount_sat,
2053 ..
2054 } => *refund_tx_amount_sat,
2055 Self::Liquid { .. } => None,
2056 }
2057 }
2058
2059 pub(crate) fn get_description(&self) -> Option<String> {
2060 match self {
2061 Self::Lightning { description, .. }
2062 | Self::Bitcoin { description, .. }
2063 | Self::Liquid { description, .. } => Some(description.clone()),
2064 }
2065 }
2066
2067 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2068 match self {
2069 Self::Liquid { asset_id, .. } => {
2070 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2071 }
2072 _ => true,
2073 }
2074 }
2075}
2076
2077#[derive(Debug, Clone, PartialEq, Serialize)]
2081pub struct Payment {
2082 pub destination: Option<String>,
2085
2086 pub tx_id: Option<String>,
2087
2088 pub unblinding_data: Option<String>,
2091
2092 pub timestamp: u32,
2098
2099 pub amount_sat: u64,
2103
2104 pub fees_sat: u64,
2118
2119 pub swapper_fees_sat: Option<u64>,
2122
2123 pub payment_type: PaymentType,
2125
2126 pub status: PaymentState,
2132
2133 pub details: PaymentDetails,
2136}
2137impl Payment {
2138 pub(crate) fn from_pending_swap(
2139 swap: PaymentSwapData,
2140 payment_type: PaymentType,
2141 payment_details: PaymentDetails,
2142 ) -> Payment {
2143 let amount_sat = match payment_type {
2144 PaymentType::Receive => swap.receiver_amount_sat,
2145 PaymentType::Send => swap.payer_amount_sat,
2146 };
2147
2148 Payment {
2149 destination: swap.invoice.clone(),
2150 tx_id: None,
2151 unblinding_data: None,
2152 timestamp: swap.created_at,
2153 amount_sat,
2154 fees_sat: swap
2155 .payer_amount_sat
2156 .saturating_sub(swap.receiver_amount_sat),
2157 swapper_fees_sat: Some(swap.swapper_fees_sat),
2158 payment_type,
2159 status: swap.status,
2160 details: payment_details,
2161 }
2162 }
2163
2164 pub(crate) fn from_tx_data(
2165 tx: PaymentTxData,
2166 balance: PaymentTxBalance,
2167 swap: Option<PaymentSwapData>,
2168 details: PaymentDetails,
2169 ) -> Payment {
2170 let (amount_sat, fees_sat) = match swap.as_ref() {
2171 Some(s) => match balance.payment_type {
2172 PaymentType::Receive => (
2176 balance.amount,
2177 s.payer_amount_sat.saturating_sub(balance.amount),
2178 ),
2179 PaymentType::Send => (
2180 s.receiver_amount_sat,
2181 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2182 ),
2183 },
2184 None => {
2185 let (amount_sat, fees_sat) = match balance.payment_type {
2186 PaymentType::Receive => (balance.amount, 0),
2187 PaymentType::Send => (balance.amount, tx.fees_sat),
2188 };
2189 match details {
2192 PaymentDetails::Liquid {
2193 asset_info: Some(ref asset_info),
2194 ..
2195 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2196 _ => (amount_sat, fees_sat),
2197 }
2198 }
2199 };
2200 Payment {
2201 tx_id: Some(tx.tx_id),
2202 unblinding_data: tx.unblinding_data,
2203 destination: match &swap {
2207 Some(PaymentSwapData {
2208 swap_type: PaymentSwapType::Receive,
2209 invoice,
2210 ..
2211 }) => invoice.clone(),
2212 Some(PaymentSwapData {
2213 swap_type: PaymentSwapType::Send,
2214 invoice,
2215 bolt12_offer,
2216 ..
2217 }) => bolt12_offer.clone().or(invoice.clone()),
2218 Some(PaymentSwapData {
2219 swap_type: PaymentSwapType::Chain,
2220 bitcoin_address,
2221 ..
2222 }) => bitcoin_address.clone(),
2223 _ => match &details {
2224 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2225 _ => None,
2226 },
2227 },
2228 timestamp: tx
2229 .timestamp
2230 .or(swap.as_ref().map(|s| s.created_at))
2231 .unwrap_or(utils::now()),
2232 amount_sat,
2233 fees_sat,
2234 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2235 payment_type: balance.payment_type,
2236 status: match &swap {
2237 Some(swap) => swap.status,
2238 None => match tx.is_confirmed {
2239 true => PaymentState::Complete,
2240 false => PaymentState::Pending,
2241 },
2242 },
2243 details,
2244 }
2245 }
2246
2247 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2248 match self.details.clone() {
2249 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2250 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2251 PaymentDetails::Liquid { .. } => None,
2252 }
2253 .flatten()
2254 }
2255}
2256
2257#[derive(Deserialize, Serialize, Clone, Debug)]
2259#[serde(rename_all = "camelCase")]
2260pub struct RecommendedFees {
2261 pub fastest_fee: u64,
2262 pub half_hour_fee: u64,
2263 pub hour_fee: u64,
2264 pub economy_fee: u64,
2265 pub minimum_fee: u64,
2266}
2267
2268#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2270pub enum BuyBitcoinProvider {
2271 #[strum(serialize = "moonpay")]
2272 Moonpay,
2273}
2274
2275#[derive(Debug, Serialize)]
2277pub struct PrepareBuyBitcoinRequest {
2278 pub provider: BuyBitcoinProvider,
2279 pub amount_sat: u64,
2280}
2281
2282#[derive(Clone, Debug, Serialize)]
2284pub struct PrepareBuyBitcoinResponse {
2285 pub provider: BuyBitcoinProvider,
2286 pub amount_sat: u64,
2287 pub fees_sat: u64,
2288}
2289
2290#[derive(Clone, Debug, Serialize)]
2292pub struct BuyBitcoinRequest {
2293 pub prepare_response: PrepareBuyBitcoinResponse,
2294 pub redirect_url: Option<String>,
2298}
2299
2300#[derive(Clone, Debug)]
2302pub struct LogEntry {
2303 pub line: String,
2304 pub level: String,
2305}
2306
2307#[derive(Clone, Debug, Serialize, Deserialize)]
2308struct InternalLeaf {
2309 pub output: String,
2310 pub version: u8,
2311}
2312impl From<InternalLeaf> for Leaf {
2313 fn from(value: InternalLeaf) -> Self {
2314 Leaf {
2315 output: value.output,
2316 version: value.version,
2317 }
2318 }
2319}
2320impl From<Leaf> for InternalLeaf {
2321 fn from(value: Leaf) -> Self {
2322 InternalLeaf {
2323 output: value.output,
2324 version: value.version,
2325 }
2326 }
2327}
2328
2329#[derive(Clone, Debug, Serialize, Deserialize)]
2330pub(super) struct InternalSwapTree {
2331 claim_leaf: InternalLeaf,
2332 refund_leaf: InternalLeaf,
2333}
2334impl From<InternalSwapTree> for SwapTree {
2335 fn from(value: InternalSwapTree) -> Self {
2336 SwapTree {
2337 claim_leaf: value.claim_leaf.into(),
2338 refund_leaf: value.refund_leaf.into(),
2339 }
2340 }
2341}
2342impl From<SwapTree> for InternalSwapTree {
2343 fn from(value: SwapTree) -> Self {
2344 InternalSwapTree {
2345 claim_leaf: value.claim_leaf.into(),
2346 refund_leaf: value.refund_leaf.into(),
2347 }
2348 }
2349}
2350
2351#[derive(Debug, Serialize)]
2353pub struct PrepareLnUrlPayRequest {
2354 pub data: LnUrlPayRequestData,
2356 pub amount: PayAmount,
2358 pub bip353_address: Option<String>,
2361 pub comment: Option<String>,
2364 pub validate_success_action_url: Option<bool>,
2367}
2368
2369#[derive(Debug, Serialize)]
2371pub struct PrepareLnUrlPayResponse {
2372 pub destination: SendDestination,
2374 pub fees_sat: u64,
2376 pub data: LnUrlPayRequestData,
2378 pub amount: PayAmount,
2380 pub comment: Option<String>,
2383 pub success_action: Option<SuccessAction>,
2386}
2387
2388#[derive(Debug, Serialize)]
2390pub struct LnUrlPayRequest {
2391 pub prepare_response: PrepareLnUrlPayResponse,
2393}
2394
2395#[derive(Serialize)]
2407#[allow(clippy::large_enum_variant)]
2408pub enum LnUrlPayResult {
2409 EndpointSuccess { data: LnUrlPaySuccessData },
2410 EndpointError { data: LnUrlErrorData },
2411 PayError { data: LnUrlPayErrorData },
2412}
2413
2414#[derive(Serialize)]
2415pub struct LnUrlPaySuccessData {
2416 pub payment: Payment,
2417 pub success_action: Option<SuccessActionProcessed>,
2418}
2419
2420#[derive(Debug, Clone)]
2421pub enum Transaction {
2422 Liquid(boltz_client::elements::Transaction),
2423 Bitcoin(boltz_client::bitcoin::Transaction),
2424}
2425
2426impl Transaction {
2427 pub(crate) fn txid(&self) -> String {
2428 match self {
2429 Transaction::Liquid(tx) => tx.txid().to_hex(),
2430 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2431 }
2432 }
2433}
2434
2435#[derive(Debug, Clone)]
2436pub enum Utxo {
2437 Liquid(
2438 Box<(
2439 boltz_client::elements::OutPoint,
2440 boltz_client::elements::TxOut,
2441 )>,
2442 ),
2443 Bitcoin(
2444 (
2445 boltz_client::bitcoin::OutPoint,
2446 boltz_client::bitcoin::TxOut,
2447 ),
2448 ),
2449}
2450
2451impl Utxo {
2452 pub(crate) fn as_bitcoin(
2453 &self,
2454 ) -> Option<&(
2455 boltz_client::bitcoin::OutPoint,
2456 boltz_client::bitcoin::TxOut,
2457 )> {
2458 match self {
2459 Utxo::Liquid(_) => None,
2460 Utxo::Bitcoin(utxo) => Some(utxo),
2461 }
2462 }
2463
2464 pub(crate) fn as_liquid(
2465 &self,
2466 ) -> Option<
2467 Box<(
2468 boltz_client::elements::OutPoint,
2469 boltz_client::elements::TxOut,
2470 )>,
2471 > {
2472 match self {
2473 Utxo::Bitcoin(_) => None,
2474 Utxo::Liquid(utxo) => Some(utxo.clone()),
2475 }
2476 }
2477}
2478
2479#[derive(Debug, Clone)]
2481pub struct FetchPaymentProposedFeesRequest {
2482 pub swap_id: String,
2483}
2484
2485#[derive(Debug, Clone, Serialize)]
2487pub struct FetchPaymentProposedFeesResponse {
2488 pub swap_id: String,
2489 pub fees_sat: u64,
2490 pub payer_amount_sat: u64,
2492 pub receiver_amount_sat: u64,
2494}
2495
2496#[derive(Debug, Clone)]
2498pub struct AcceptPaymentProposedFeesRequest {
2499 pub response: FetchPaymentProposedFeesResponse,
2500}
2501
2502#[derive(Clone, Debug)]
2503pub struct History<T> {
2504 pub txid: T,
2505 pub height: i32,
2510}
2511pub(crate) type LBtcHistory = History<elements::Txid>;
2512pub(crate) type BtcHistory = History<bitcoin::Txid>;
2513
2514impl<T> History<T> {
2515 pub(crate) fn confirmed(&self) -> bool {
2516 self.height > 0
2517 }
2518}
2519#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2520impl From<electrum_client::GetHistoryRes> for BtcHistory {
2521 fn from(value: electrum_client::GetHistoryRes) -> Self {
2522 Self {
2523 txid: value.tx_hash,
2524 height: value.height,
2525 }
2526 }
2527}
2528impl From<lwk_wollet::History> for LBtcHistory {
2529 fn from(value: lwk_wollet::History) -> Self {
2530 Self::from(&value)
2531 }
2532}
2533impl From<&lwk_wollet::History> for LBtcHistory {
2534 fn from(value: &lwk_wollet::History) -> Self {
2535 Self {
2536 txid: value.txid,
2537 height: value.height,
2538 }
2539 }
2540}
2541pub(crate) type BtcScript = bitcoin::ScriptBuf;
2542pub(crate) type LBtcScript = elements::Script;
2543
2544#[derive(Clone, Debug)]
2545pub struct BtcScriptBalance {
2546 pub confirmed: u64,
2548 pub unconfirmed: i64,
2552}
2553#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2554impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2555 fn from(val: electrum_client::GetBalanceRes) -> Self {
2556 Self {
2557 confirmed: val.confirmed,
2558 unconfirmed: val.unconfirmed,
2559 }
2560 }
2561}
2562
2563pub(crate) struct GetSyncContextRequest {
2564 pub partial_sync: Option<bool>,
2565 pub last_liquid_tip: u32,
2566 pub last_bitcoin_tip: u32,
2567}
2568
2569pub(crate) struct SyncContext {
2570 pub maybe_liquid_tip: Option<u32>,
2571 pub maybe_bitcoin_tip: Option<u32>,
2572 pub recoverable_swaps: Vec<Swap>,
2573 pub is_new_liquid_block: bool,
2574 pub is_new_bitcoin_block: bool,
2575}
2576
2577pub(crate) struct TaskHandle {
2578 pub name: String,
2579 pub handle: tokio::task::JoinHandle<()>,
2580}
2581
2582#[macro_export]
2583macro_rules! get_updated_fields {
2584 ($($var:ident),* $(,)?) => {{
2585 let mut options = Vec::new();
2586 $(
2587 if $var.is_some() {
2588 options.push(stringify!($var).to_string());
2589 }
2590 )*
2591 match options.len() > 0 {
2592 true => Some(options),
2593 false => None,
2594 }
2595 }};
2596}