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 maybe_sync::{MaybeSend, MaybeSync};
15use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
16use rusqlite::ToSql;
17use sdk_common::prelude::*;
18use sdk_common::utils::Arc;
19use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
20use serde::{Deserialize, Serialize};
21use std::cmp::PartialEq;
22use std::path::PathBuf;
23use std::str::FromStr;
24use strum_macros::{Display, EnumString};
25use tokio_with_wasm::alias as tokio;
26
27use crate::{
28 bitcoin,
29 chain::bitcoin::esplora::EsploraBitcoinChainService,
30 chain::liquid::esplora::EsploraLiquidChainService,
31 chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
32 elements,
33 error::{PaymentError, SdkError, SdkResult},
34 persist::model::PaymentTxBalance,
35 prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
36 receive_swap::DEFAULT_ZERO_CONF_MAX_SAT,
37 side_swap::api::{SIDESWAP_MAINNET_URL, SIDESWAP_TESTNET_URL},
38 utils,
39};
40
41pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
43pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
44pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
45pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api";
46pub const BREEZ_SWAP_PROXY_URL: &str = "https://swap.breez.technology/v2";
47pub const DEFAULT_ONCHAIN_FEE_RATE_LEEWAY_SAT: u64 = 500;
48
49const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
50
51#[derive(Clone, Debug, Serialize)]
52pub enum BlockchainExplorer {
53 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
54 Electrum { url: String },
55 Esplora {
56 url: String,
57 use_waterfalls: bool,
59 },
60}
61
62#[derive(Clone, Debug, Serialize)]
64pub struct Config {
65 pub liquid_explorer: BlockchainExplorer,
66 pub bitcoin_explorer: BlockchainExplorer,
67 pub working_dir: String,
71 pub network: LiquidNetwork,
72 pub payment_timeout_sec: u64,
74 pub sync_service_url: Option<String>,
77 pub zero_conf_max_amount_sat: Option<u64>,
80 pub breez_api_key: Option<String>,
82 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
86 pub use_default_external_input_parsers: bool,
90 pub onchain_fee_rate_leeway_sat: Option<u64>,
97 pub asset_metadata: Option<Vec<AssetMetadata>>,
102 pub sideswap_api_key: Option<String>,
104 pub use_magic_routing_hints: bool,
106}
107
108impl Config {
109 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
110 pub fn mainnet(breez_api_key: Option<String>) -> Self {
111 Config {
112 liquid_explorer: BlockchainExplorer::Electrum {
113 url: "elements-mainnet.breez.technology:50002".to_string(),
114 },
115 bitcoin_explorer: BlockchainExplorer::Electrum {
116 url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
117 },
118 working_dir: ".".to_string(),
119 network: LiquidNetwork::Mainnet,
120 payment_timeout_sec: 15,
121 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
122 zero_conf_max_amount_sat: None,
123 breez_api_key,
124 external_input_parsers: None,
125 use_default_external_input_parsers: true,
126 onchain_fee_rate_leeway_sat: None,
127 asset_metadata: None,
128 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
129 use_magic_routing_hints: true,
130 }
131 }
132
133 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
134 Config {
135 liquid_explorer: BlockchainExplorer::Esplora {
136 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
137 use_waterfalls: true,
138 },
139 bitcoin_explorer: BlockchainExplorer::Esplora {
140 url: "https://blockstream.info/api/".to_string(),
141 use_waterfalls: false,
142 },
143 working_dir: ".".to_string(),
144 network: LiquidNetwork::Mainnet,
145 payment_timeout_sec: 15,
146 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
147 zero_conf_max_amount_sat: None,
148 breez_api_key,
149 external_input_parsers: None,
150 use_default_external_input_parsers: true,
151 onchain_fee_rate_leeway_sat: None,
152 asset_metadata: None,
153 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
154 use_magic_routing_hints: true,
155 }
156 }
157
158 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
159 pub fn testnet(breez_api_key: Option<String>) -> Self {
160 Config {
161 liquid_explorer: BlockchainExplorer::Electrum {
162 url: "elements-testnet.blockstream.info:50002".to_string(),
163 },
164 bitcoin_explorer: BlockchainExplorer::Electrum {
165 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
166 },
167 working_dir: ".".to_string(),
168 network: LiquidNetwork::Testnet,
169 payment_timeout_sec: 15,
170 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
171 zero_conf_max_amount_sat: None,
172 breez_api_key,
173 external_input_parsers: None,
174 use_default_external_input_parsers: true,
175 onchain_fee_rate_leeway_sat: None,
176 asset_metadata: None,
177 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
178 use_magic_routing_hints: true,
179 }
180 }
181
182 pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
183 Config {
184 liquid_explorer: BlockchainExplorer::Esplora {
185 url: "https://blockstream.info/liquidtestnet/api".to_string(),
186 use_waterfalls: false,
187 },
188 bitcoin_explorer: BlockchainExplorer::Esplora {
189 url: "https://blockstream.info/testnet/api/".to_string(),
190 use_waterfalls: false,
191 },
192 working_dir: ".".to_string(),
193 network: LiquidNetwork::Testnet,
194 payment_timeout_sec: 15,
195 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
196 zero_conf_max_amount_sat: None,
197 breez_api_key,
198 external_input_parsers: None,
199 use_default_external_input_parsers: true,
200 onchain_fee_rate_leeway_sat: None,
201 asset_metadata: None,
202 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
203 use_magic_routing_hints: true,
204 }
205 }
206
207 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
208 pub fn regtest() -> Self {
209 Config {
210 liquid_explorer: BlockchainExplorer::Electrum {
211 url: "localhost:19002".to_string(),
212 },
213 bitcoin_explorer: BlockchainExplorer::Electrum {
214 url: "localhost:19001".to_string(),
215 },
216 working_dir: ".".to_string(),
217 network: LiquidNetwork::Regtest,
218 payment_timeout_sec: 15,
219 sync_service_url: Some("http://localhost:8088".to_string()),
220 zero_conf_max_amount_sat: None,
221 breez_api_key: None,
222 external_input_parsers: None,
223 use_default_external_input_parsers: true,
224 onchain_fee_rate_leeway_sat: None,
225 asset_metadata: None,
226 sideswap_api_key: None,
227 use_magic_routing_hints: true,
228 }
229 }
230
231 pub fn regtest_esplora() -> Self {
232 Config {
233 liquid_explorer: BlockchainExplorer::Esplora {
234 url: "http://localhost:3120/api".to_string(),
235 use_waterfalls: true,
236 },
237 bitcoin_explorer: BlockchainExplorer::Esplora {
238 url: "http://localhost:4002/api".to_string(),
239 use_waterfalls: false,
240 },
241 working_dir: ".".to_string(),
242 network: LiquidNetwork::Regtest,
243 payment_timeout_sec: 15,
244 sync_service_url: Some("http://localhost:8089".to_string()),
245 zero_conf_max_amount_sat: None,
246 breez_api_key: None,
247 external_input_parsers: None,
248 use_default_external_input_parsers: true,
249 onchain_fee_rate_leeway_sat: None,
250 asset_metadata: None,
251 sideswap_api_key: None,
252 use_magic_routing_hints: true,
253 }
254 }
255
256 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
257 Ok(PathBuf::from(base_dir)
258 .join(match self.network {
259 LiquidNetwork::Mainnet => "mainnet",
260 LiquidNetwork::Testnet => "testnet",
261 LiquidNetwork::Regtest => "regtest",
262 })
263 .join(fingerprint_hex)
264 .to_str()
265 .ok_or(anyhow::anyhow!(
266 "Could not get retrieve current wallet directory"
267 ))?
268 .to_string())
269 }
270
271 pub fn zero_conf_max_amount_sat(&self) -> u64 {
272 self.zero_conf_max_amount_sat
273 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
274 }
275
276 pub(crate) fn lbtc_asset_id(&self) -> String {
277 utils::lbtc_asset_id(self.network).to_string()
278 }
279
280 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
281 let mut external_input_parsers = Vec::new();
282 if self.use_default_external_input_parsers {
283 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
284 .iter()
285 .map(|(id, regex, url)| ExternalInputParser {
286 provider_id: id.to_string(),
287 input_regex: regex.to_string(),
288 parser_url: url.to_string(),
289 })
290 .collect::<Vec<_>>();
291 external_input_parsers.extend(default_parsers);
292 }
293 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
294
295 external_input_parsers
296 }
297
298 pub(crate) fn default_boltz_url(&self) -> &str {
299 match self.network {
300 LiquidNetwork::Mainnet => {
301 if self.breez_api_key.is_some() {
302 BREEZ_SWAP_PROXY_URL
303 } else {
304 BOLTZ_MAINNET_URL_V2
305 }
306 }
307 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
308 LiquidNetwork::Regtest => "http://localhost:8387/v2",
310 }
311 }
312
313 pub fn sync_enabled(&self) -> bool {
314 self.sync_service_url.is_some()
315 }
316
317 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
318 match self.bitcoin_explorer {
319 BlockchainExplorer::Esplora { .. } => {
320 Arc::new(EsploraBitcoinChainService::new(self.clone()))
321 }
322 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
323 BlockchainExplorer::Electrum { .. } => Arc::new(
324 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
325 ),
326 }
327 }
328
329 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
330 match &self.liquid_explorer {
331 BlockchainExplorer::Esplora { url, .. } => {
332 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
333 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")
334 }
335 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
336 }
337 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
338 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
339 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
340 )),
341 }
342 }
343
344 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
345 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
346 match self.network {
347 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
348 LiquidNetwork::Regtest => (false, false),
349 }
350 }
351
352 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
353 pub(crate) fn electrum_client(
354 &self,
355 url: &str,
356 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
357 let (tls, validate_domain) = self.electrum_tls_options();
358 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
359 lwk_wollet::ElectrumClient::with_options(
360 &electrum_url,
361 lwk_wollet::ElectrumOptions { timeout: Some(3) },
362 )
363 }
364
365 pub(crate) fn sideswap_url(&self) -> &'static str {
366 match self.network {
367 LiquidNetwork::Mainnet => SIDESWAP_MAINNET_URL,
368 LiquidNetwork::Testnet => SIDESWAP_TESTNET_URL,
369 LiquidNetwork::Regtest => unimplemented!(),
370 }
371 }
372}
373
374#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
377pub enum LiquidNetwork {
378 Mainnet,
380 Testnet,
382 Regtest,
384}
385impl LiquidNetwork {
386 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
387 match self {
388 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
389 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
390 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
391 }
392 }
393}
394
395impl From<LiquidNetwork> for ElementsNetwork {
396 fn from(value: LiquidNetwork) -> Self {
397 match value {
398 LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
399 LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
400 LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
401 policy_asset: AssetId::from_str(
402 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
403 )
404 .unwrap(),
405 },
406 }
407 }
408}
409
410impl From<LiquidNetwork> for Chain {
411 fn from(value: LiquidNetwork) -> Self {
412 Chain::Liquid(value.into())
413 }
414}
415
416impl From<LiquidNetwork> for LiquidChain {
417 fn from(value: LiquidNetwork) -> Self {
418 match value {
419 LiquidNetwork::Mainnet => LiquidChain::Liquid,
420 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
421 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
422 }
423 }
424}
425
426impl TryFrom<&str> for LiquidNetwork {
427 type Error = anyhow::Error;
428
429 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
430 match value.to_lowercase().as_str() {
431 "mainnet" => Ok(LiquidNetwork::Mainnet),
432 "testnet" => Ok(LiquidNetwork::Testnet),
433 "regtest" => Ok(LiquidNetwork::Regtest),
434 _ => Err(anyhow!("Invalid network")),
435 }
436 }
437}
438
439impl From<LiquidNetwork> for Network {
440 fn from(value: LiquidNetwork) -> Self {
441 match value {
442 LiquidNetwork::Mainnet => Self::Bitcoin,
443 LiquidNetwork::Testnet => Self::Testnet,
444 LiquidNetwork::Regtest => Self::Regtest,
445 }
446 }
447}
448
449impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
450 fn from(value: LiquidNetwork) -> Self {
451 match value {
452 LiquidNetwork::Mainnet => Self::Bitcoin,
453 LiquidNetwork::Testnet => Self::Testnet,
454 LiquidNetwork::Regtest => Self::Regtest,
455 }
456 }
457}
458
459impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
460 fn from(value: LiquidNetwork) -> Self {
461 match value {
462 LiquidNetwork::Mainnet => Self::Bitcoin,
463 LiquidNetwork::Testnet => Self::Testnet,
464 LiquidNetwork::Regtest => Self::Regtest,
465 }
466 }
467}
468
469pub trait EventListener: MaybeSend + MaybeSync {
471 fn on_event(&self, e: SdkEvent);
472}
473
474#[derive(Clone, Debug, PartialEq)]
477pub enum SdkEvent {
478 PaymentFailed {
479 details: Payment,
480 },
481 PaymentPending {
482 details: Payment,
483 },
484 PaymentRefundable {
485 details: Payment,
486 },
487 PaymentRefunded {
488 details: Payment,
489 },
490 PaymentRefundPending {
491 details: Payment,
492 },
493 PaymentSucceeded {
494 details: Payment,
495 },
496 PaymentWaitingConfirmation {
497 details: Payment,
498 },
499 PaymentWaitingFeeAcceptance {
500 details: Payment,
501 },
502 Synced,
504 DataSynced {
506 did_pull_new_records: bool,
508 },
509}
510
511#[derive(thiserror::Error, Debug)]
512pub enum SignerError {
513 #[error("Signer error: {err}")]
514 Generic { err: String },
515}
516
517impl From<anyhow::Error> for SignerError {
518 fn from(err: anyhow::Error) -> Self {
519 SignerError::Generic {
520 err: err.to_string(),
521 }
522 }
523}
524
525impl From<bip32::Error> for SignerError {
526 fn from(err: bip32::Error) -> Self {
527 SignerError::Generic {
528 err: err.to_string(),
529 }
530 }
531}
532
533pub trait Signer: MaybeSend + MaybeSync {
536 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
539
540 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
546
547 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
549
550 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
552
553 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
555
556 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
559
560 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
562
563 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
565}
566
567pub struct ConnectRequest {
570 pub config: Config,
572 pub mnemonic: Option<String>,
574 pub passphrase: Option<String>,
576 pub seed: Option<Vec<u8>>,
578}
579
580pub struct ConnectWithSignerRequest {
581 pub config: Config,
582}
583
584#[derive(Clone, Debug)]
587pub(crate) struct ReservedAddress {
588 pub(crate) address: String,
590 pub(crate) expiry_block_height: u32,
592}
593
594#[derive(Clone, Debug, Serialize)]
596pub enum PaymentMethod {
597 #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
598 Lightning,
599 Bolt11Invoice,
600 Bolt12Offer,
601 BitcoinAddress,
602 LiquidAddress,
603}
604
605#[derive(Debug, Serialize, Clone)]
606pub enum ReceiveAmount {
607 Bitcoin { payer_amount_sat: u64 },
609
610 Asset {
612 asset_id: String,
613 payer_amount: Option<f64>,
614 },
615}
616
617#[derive(Debug, Serialize)]
619pub struct PrepareReceiveRequest {
620 pub payment_method: PaymentMethod,
621 pub amount: Option<ReceiveAmount>,
623}
624
625#[derive(Debug, Serialize, Clone)]
627pub struct PrepareReceiveResponse {
628 pub payment_method: PaymentMethod,
629 pub fees_sat: u64,
638 pub amount: Option<ReceiveAmount>,
640 pub min_payer_amount_sat: Option<u64>,
644 pub max_payer_amount_sat: Option<u64>,
648 pub swapper_feerate: Option<f64>,
652}
653
654#[derive(Debug, Serialize)]
656pub struct ReceivePaymentRequest {
657 pub prepare_response: PrepareReceiveResponse,
658 pub description: Option<String>,
660 pub use_description_hash: Option<bool>,
662 pub payer_note: Option<String>,
664}
665
666#[derive(Debug, Serialize)]
668pub struct ReceivePaymentResponse {
669 pub destination: String,
672}
673
674#[derive(Debug, Serialize)]
676pub struct CreateBolt12InvoiceRequest {
677 pub offer: String,
679 pub invoice_request: String,
681}
682
683#[derive(Debug, Serialize, Clone)]
685pub struct CreateBolt12InvoiceResponse {
686 pub invoice: String,
688}
689
690#[derive(Debug, Serialize)]
692pub struct Limits {
693 pub min_sat: u64,
694 pub max_sat: u64,
695 pub max_zero_conf_sat: u64,
696}
697
698#[derive(Debug, Serialize)]
700pub struct LightningPaymentLimitsResponse {
701 pub send: Limits,
703 pub receive: Limits,
705}
706
707#[derive(Debug, Serialize)]
709pub struct OnchainPaymentLimitsResponse {
710 pub send: Limits,
712 pub receive: Limits,
714}
715
716#[derive(Debug, Serialize, Clone)]
718pub struct PrepareSendRequest {
719 pub destination: String,
722 pub amount: Option<PayAmount>,
725}
726
727#[derive(Clone, Debug, Serialize)]
729pub enum SendDestination {
730 LiquidAddress {
731 address_data: liquid::LiquidAddressData,
732 bip353_address: Option<String>,
734 },
735 Bolt11 {
736 invoice: LNInvoice,
737 bip353_address: Option<String>,
739 },
740 Bolt12 {
741 offer: LNOffer,
742 receiver_amount_sat: u64,
743 bip353_address: Option<String>,
745 },
746}
747
748#[derive(Debug, Serialize, Clone)]
750pub struct PrepareSendResponse {
751 pub destination: SendDestination,
752 pub amount: Option<PayAmount>,
754 pub fees_sat: Option<u64>,
757 pub estimated_asset_fees: Option<f64>,
761 pub exchange_amount_sat: Option<u64>,
764}
765
766#[derive(Debug, Serialize)]
768pub struct SendPaymentRequest {
769 pub prepare_response: PrepareSendResponse,
770 pub use_asset_fees: Option<bool>,
772 pub payer_note: Option<String>,
774}
775
776#[derive(Debug, Serialize)]
778pub struct SendPaymentResponse {
779 pub payment: Payment,
780}
781
782pub(crate) struct SendPaymentViaSwapRequest {
783 pub(crate) invoice: String,
784 pub(crate) bolt12_offer: Option<String>,
785 pub(crate) payment_hash: String,
786 pub(crate) description: Option<String>,
787 pub(crate) receiver_amount_sat: u64,
788 pub(crate) fees_sat: u64,
789}
790
791pub(crate) struct PayLiquidRequest {
792 pub address_data: LiquidAddressData,
793 pub to_asset: String,
794 pub receiver_amount_sat: u64,
795 pub asset_pay_fees: bool,
796 pub fees_sat: Option<u64>,
797}
798
799pub(crate) struct PaySideSwapRequest {
800 pub address_data: LiquidAddressData,
801 pub to_asset: String,
802 pub receiver_amount_sat: u64,
803 pub fees_sat: u64,
804 pub amount: Option<PayAmount>,
805}
806
807#[derive(Debug, Serialize, Clone)]
809pub enum PayAmount {
810 Bitcoin { receiver_amount_sat: u64 },
812
813 Asset {
815 to_asset: String,
817 receiver_amount: f64,
818 estimate_asset_fees: Option<bool>,
819 from_asset: Option<String>,
822 },
823
824 Drain,
826}
827
828impl PayAmount {
829 pub(crate) fn is_sideswap_payment(&self) -> bool {
830 match self {
831 PayAmount::Asset {
832 to_asset,
833 from_asset,
834 ..
835 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
836 _ => false,
837 }
838 }
839}
840
841#[derive(Debug, Serialize, Clone)]
843pub struct PreparePayOnchainRequest {
844 pub amount: PayAmount,
846 pub fee_rate_sat_per_vbyte: Option<u32>,
848}
849
850#[derive(Debug, Serialize, Clone)]
852pub struct PreparePayOnchainResponse {
853 pub receiver_amount_sat: u64,
854 pub claim_fees_sat: u64,
855 pub total_fees_sat: u64,
856}
857
858#[derive(Debug, Serialize)]
860pub struct PayOnchainRequest {
861 pub address: String,
862 pub prepare_response: PreparePayOnchainResponse,
863}
864
865#[derive(Debug, Serialize)]
867pub struct PrepareRefundRequest {
868 pub swap_address: String,
870 pub refund_address: String,
872 pub fee_rate_sat_per_vbyte: u32,
874}
875
876#[derive(Debug, Serialize)]
878pub struct PrepareRefundResponse {
879 pub tx_vsize: u32,
880 pub tx_fee_sat: u64,
881 pub last_refund_tx_id: Option<String>,
883}
884
885#[derive(Debug, Serialize)]
887pub struct RefundRequest {
888 pub swap_address: String,
890 pub refund_address: String,
892 pub fee_rate_sat_per_vbyte: u32,
894}
895
896#[derive(Debug, Serialize)]
898pub struct RefundResponse {
899 pub refund_tx_id: String,
900}
901
902#[derive(Clone, Debug, Default, Serialize, Deserialize)]
904pub struct AssetBalance {
905 pub asset_id: String,
906 pub balance_sat: u64,
907 pub name: Option<String>,
908 pub ticker: Option<String>,
909 pub balance: Option<f64>,
910}
911
912#[derive(Debug, Serialize, Deserialize, Default)]
913pub struct BlockchainInfo {
914 pub liquid_tip: u32,
915 pub bitcoin_tip: u32,
916}
917
918#[derive(Copy, Clone)]
919pub(crate) struct ChainTips {
920 pub liquid_tip: u32,
921 pub bitcoin_tip: Option<u32>,
922}
923
924#[derive(Debug, Serialize, Deserialize)]
925pub struct WalletInfo {
926 pub balance_sat: u64,
928 pub pending_send_sat: u64,
930 pub pending_receive_sat: u64,
932 pub fingerprint: String,
934 pub pubkey: String,
936 #[serde(default)]
938 pub asset_balances: Vec<AssetBalance>,
939}
940
941impl WalletInfo {
942 pub(crate) fn validate_sufficient_funds(
943 &self,
944 network: LiquidNetwork,
945 amount_sat: u64,
946 fees_sat: Option<u64>,
947 asset_id: &str,
948 ) -> Result<(), PaymentError> {
949 let fees_sat = fees_sat.unwrap_or(0);
950 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
951 ensure_sdk!(
952 amount_sat + fees_sat <= self.balance_sat,
953 PaymentError::InsufficientFunds
954 );
955 } else {
956 match self
957 .asset_balances
958 .iter()
959 .find(|ab| ab.asset_id.eq(asset_id))
960 {
961 Some(asset_balance) => ensure_sdk!(
962 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
963 PaymentError::InsufficientFunds
964 ),
965 None => return Err(PaymentError::InsufficientFunds),
966 }
967 }
968 Ok(())
969 }
970}
971
972#[derive(Debug, Serialize, Deserialize)]
974pub struct GetInfoResponse {
975 pub wallet_info: WalletInfo,
977 #[serde(default)]
979 pub blockchain_info: BlockchainInfo,
980}
981
982#[derive(Clone, Debug, PartialEq)]
984pub struct SignMessageRequest {
985 pub message: String,
986}
987
988#[derive(Clone, Debug, PartialEq)]
990pub struct SignMessageResponse {
991 pub signature: String,
992}
993
994#[derive(Clone, Debug, PartialEq)]
996pub struct CheckMessageRequest {
997 pub message: String,
999 pub pubkey: String,
1001 pub signature: String,
1003}
1004
1005#[derive(Clone, Debug, PartialEq)]
1007pub struct CheckMessageResponse {
1008 pub is_valid: bool,
1011}
1012
1013#[derive(Debug, Serialize)]
1015pub struct BackupRequest {
1016 pub backup_path: Option<String>,
1023}
1024
1025#[derive(Debug, Serialize)]
1027pub struct RestoreRequest {
1028 pub backup_path: Option<String>,
1029}
1030
1031#[derive(Default)]
1033pub struct ListPaymentsRequest {
1034 pub filters: Option<Vec<PaymentType>>,
1035 pub states: Option<Vec<PaymentState>>,
1036 pub from_timestamp: Option<i64>,
1038 pub to_timestamp: Option<i64>,
1040 pub offset: Option<u32>,
1041 pub limit: Option<u32>,
1042 pub details: Option<ListPaymentDetails>,
1043 pub sort_ascending: Option<bool>,
1044}
1045
1046#[derive(Debug, Serialize)]
1048pub enum ListPaymentDetails {
1049 Liquid {
1051 asset_id: Option<String>,
1053 destination: Option<String>,
1055 },
1056
1057 Bitcoin {
1059 address: Option<String>,
1061 },
1062}
1063
1064#[derive(Debug, Serialize)]
1066pub enum GetPaymentRequest {
1067 PaymentHash { payment_hash: String },
1069 SwapId { swap_id: String },
1071}
1072
1073#[sdk_macros::async_trait]
1075pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1076 async fn on_bitcoin_block(&self, height: u32);
1077 async fn on_liquid_block(&self, height: u32);
1078}
1079
1080#[derive(Clone, Debug)]
1082pub enum Swap {
1083 Chain(ChainSwap),
1084 Send(SendSwap),
1085 Receive(ReceiveSwap),
1086}
1087impl Swap {
1088 pub(crate) fn id(&self) -> String {
1089 match &self {
1090 Swap::Chain(ChainSwap { id, .. })
1091 | Swap::Send(SendSwap { id, .. })
1092 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1093 }
1094 }
1095
1096 pub(crate) fn version(&self) -> u64 {
1097 match self {
1098 Swap::Chain(ChainSwap { metadata, .. })
1099 | Swap::Send(SendSwap { metadata, .. })
1100 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1101 }
1102 }
1103
1104 pub(crate) fn set_version(&mut self, version: u64) {
1105 match self {
1106 Swap::Chain(chain_swap) => {
1107 chain_swap.metadata.version = version;
1108 }
1109 Swap::Send(send_swap) => {
1110 send_swap.metadata.version = version;
1111 }
1112 Swap::Receive(receive_swap) => {
1113 receive_swap.metadata.version = version;
1114 }
1115 }
1116 }
1117
1118 pub(crate) fn last_updated_at(&self) -> u32 {
1119 match self {
1120 Swap::Chain(ChainSwap { metadata, .. })
1121 | Swap::Send(SendSwap { metadata, .. })
1122 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1123 }
1124 }
1125}
1126impl From<ChainSwap> for Swap {
1127 fn from(swap: ChainSwap) -> Self {
1128 Self::Chain(swap)
1129 }
1130}
1131impl From<SendSwap> for Swap {
1132 fn from(swap: SendSwap) -> Self {
1133 Self::Send(swap)
1134 }
1135}
1136impl From<ReceiveSwap> for Swap {
1137 fn from(swap: ReceiveSwap) -> Self {
1138 Self::Receive(swap)
1139 }
1140}
1141
1142#[derive(Clone, Debug)]
1143pub(crate) enum SwapScriptV2 {
1144 Bitcoin(BtcSwapScript),
1145 Liquid(LBtcSwapScript),
1146}
1147impl SwapScriptV2 {
1148 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1149 match self {
1150 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1151 _ => Err(anyhow!("Invalid chain")),
1152 }
1153 }
1154
1155 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1156 match self {
1157 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1158 _ => Err(anyhow!("Invalid chain")),
1159 }
1160 }
1161}
1162
1163#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1164pub enum Direction {
1165 Incoming = 0,
1166 Outgoing = 1,
1167}
1168impl ToSql for Direction {
1169 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1170 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1171 }
1172}
1173impl FromSql for Direction {
1174 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1175 match value {
1176 ValueRef::Integer(i) => match i as u8 {
1177 0 => Ok(Direction::Incoming),
1178 1 => Ok(Direction::Outgoing),
1179 _ => Err(FromSqlError::OutOfRange(i)),
1180 },
1181 _ => Err(FromSqlError::InvalidType),
1182 }
1183 }
1184}
1185
1186#[derive(Clone, Debug, Default)]
1187pub(crate) struct SwapMetadata {
1188 pub(crate) version: u64,
1190 pub(crate) last_updated_at: u32,
1191 pub(crate) is_local: bool,
1192}
1193
1194#[derive(Clone, Debug, Derivative)]
1198#[derivative(PartialEq)]
1199pub struct ChainSwap {
1200 pub(crate) id: String,
1201 pub(crate) direction: Direction,
1202 pub(crate) claim_address: Option<String>,
1205 pub(crate) lockup_address: String,
1206 pub(crate) refund_address: Option<String>,
1208 pub(crate) timeout_block_height: u32,
1209 pub(crate) preimage: String,
1210 pub(crate) description: Option<String>,
1211 pub(crate) payer_amount_sat: u64,
1213 pub(crate) actual_payer_amount_sat: Option<u64>,
1216 pub(crate) receiver_amount_sat: u64,
1218 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1220 pub(crate) claim_fees_sat: u64,
1221 pub(crate) pair_fees_json: String,
1223 pub(crate) accept_zero_conf: bool,
1224 pub(crate) create_response_json: String,
1226 pub(crate) server_lockup_tx_id: Option<String>,
1228 pub(crate) user_lockup_tx_id: Option<String>,
1230 pub(crate) claim_tx_id: Option<String>,
1232 pub(crate) refund_tx_id: Option<String>,
1234 pub(crate) created_at: u32,
1235 pub(crate) state: PaymentState,
1236 pub(crate) claim_private_key: String,
1237 pub(crate) refund_private_key: String,
1238 pub(crate) auto_accepted_fees: bool,
1239 #[derivative(PartialEq = "ignore")]
1241 pub(crate) metadata: SwapMetadata,
1242}
1243impl ChainSwap {
1244 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1245 utils::decode_keypair(&self.claim_private_key)
1246 }
1247
1248 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1249 utils::decode_keypair(&self.refund_private_key)
1250 }
1251
1252 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1253 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1254 serde_json::from_str(&self.create_response_json).map_err(|e| {
1255 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1256 })?;
1257
1258 Ok(CreateChainResponse {
1259 id: self.id.clone(),
1260 claim_details: internal_create_response.claim_details,
1261 lockup_details: internal_create_response.lockup_details,
1262 })
1263 }
1264
1265 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1266 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1267 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1268
1269 Ok(pair)
1270 }
1271
1272 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1273 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1274 let our_pubkey = self.get_claim_keypair()?.public_key();
1275 let swap_script = match self.direction {
1276 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1277 Side::Claim,
1278 chain_swap_details,
1279 our_pubkey.into(),
1280 )?),
1281 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1282 Side::Claim,
1283 chain_swap_details,
1284 our_pubkey.into(),
1285 )?),
1286 };
1287 Ok(swap_script)
1288 }
1289
1290 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1291 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1292 let our_pubkey = self.get_refund_keypair()?.public_key();
1293 let swap_script = match self.direction {
1294 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1295 Side::Lockup,
1296 chain_swap_details,
1297 our_pubkey.into(),
1298 )?),
1299 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1300 Side::Lockup,
1301 chain_swap_details,
1302 our_pubkey.into(),
1303 )?),
1304 };
1305 Ok(swap_script)
1306 }
1307
1308 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1310 &self,
1311 network: LiquidNetwork,
1312 ) -> SdkResult<ScriptBuf> {
1313 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1314 let script_pubkey = swap_script
1315 .to_address(network.as_bitcoin_chain())
1316 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1317 .script_pubkey();
1318 Ok(script_pubkey)
1319 }
1320
1321 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1322 RefundableSwap {
1323 swap_address: self.lockup_address.clone(),
1324 timestamp: self.created_at,
1325 amount_sat,
1326 last_refund_tx_id: self.refund_tx_id.clone(),
1327 }
1328 }
1329
1330 pub(crate) fn from_boltz_struct_to_json(
1331 create_response: &CreateChainResponse,
1332 expected_swap_id: &str,
1333 ) -> Result<String, PaymentError> {
1334 let internal_create_response =
1335 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1336 create_response,
1337 expected_swap_id,
1338 )?;
1339
1340 let create_response_json =
1341 serde_json::to_string(&internal_create_response).map_err(|e| {
1342 PaymentError::Generic {
1343 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1344 }
1345 })?;
1346
1347 Ok(create_response_json)
1348 }
1349
1350 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1351 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1352 }
1353}
1354
1355#[derive(Clone, Debug, Default)]
1356pub(crate) struct ChainSwapUpdate {
1357 pub(crate) swap_id: String,
1358 pub(crate) to_state: PaymentState,
1359 pub(crate) server_lockup_tx_id: Option<String>,
1360 pub(crate) user_lockup_tx_id: Option<String>,
1361 pub(crate) claim_address: Option<String>,
1362 pub(crate) claim_tx_id: Option<String>,
1363 pub(crate) refund_tx_id: Option<String>,
1364}
1365
1366#[derive(Clone, Debug, Derivative)]
1368#[derivative(PartialEq)]
1369pub struct SendSwap {
1370 pub(crate) id: String,
1371 pub(crate) invoice: String,
1373 pub(crate) bolt12_offer: Option<String>,
1375 pub(crate) payment_hash: Option<String>,
1376 pub(crate) destination_pubkey: Option<String>,
1377 pub(crate) description: Option<String>,
1378 pub(crate) preimage: Option<String>,
1379 pub(crate) payer_amount_sat: u64,
1380 pub(crate) receiver_amount_sat: u64,
1381 pub(crate) pair_fees_json: String,
1383 pub(crate) create_response_json: String,
1385 pub(crate) lockup_tx_id: Option<String>,
1387 pub(crate) refund_address: Option<String>,
1389 pub(crate) refund_tx_id: Option<String>,
1391 pub(crate) created_at: u32,
1392 pub(crate) timeout_block_height: u64,
1393 pub(crate) state: PaymentState,
1394 pub(crate) refund_private_key: String,
1395 #[derivative(PartialEq = "ignore")]
1397 pub(crate) metadata: SwapMetadata,
1398}
1399impl SendSwap {
1400 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1401 utils::decode_keypair(&self.refund_private_key)
1402 }
1403
1404 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1405 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1406 serde_json::from_str(&self.create_response_json).map_err(|e| {
1407 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1408 })?;
1409
1410 let res = CreateSubmarineResponse {
1411 id: self.id.clone(),
1412 accept_zero_conf: internal_create_response.accept_zero_conf,
1413 address: internal_create_response.address.clone(),
1414 bip21: internal_create_response.bip21.clone(),
1415 claim_public_key: crate::utils::json_to_pubkey(
1416 &internal_create_response.claim_public_key,
1417 )?,
1418 expected_amount: internal_create_response.expected_amount,
1419 referral_id: internal_create_response.referral_id,
1420 swap_tree: internal_create_response.swap_tree.clone().into(),
1421 timeout_block_height: internal_create_response.timeout_block_height,
1422 blinding_key: internal_create_response.blinding_key.clone(),
1423 };
1424 Ok(res)
1425 }
1426
1427 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1428 LBtcSwapScript::submarine_from_swap_resp(
1429 &self.get_boltz_create_response()?,
1430 self.get_refund_keypair()?.public_key().into(),
1431 )
1432 .map_err(|e| {
1433 SdkError::generic(format!(
1434 "Failed to create swap script for Send Swap {}: {e:?}",
1435 self.id
1436 ))
1437 })
1438 }
1439
1440 pub(crate) fn from_boltz_struct_to_json(
1441 create_response: &CreateSubmarineResponse,
1442 expected_swap_id: &str,
1443 ) -> Result<String, PaymentError> {
1444 let internal_create_response =
1445 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1446 create_response,
1447 expected_swap_id,
1448 )?;
1449
1450 let create_response_json =
1451 serde_json::to_string(&internal_create_response).map_err(|e| {
1452 PaymentError::Generic {
1453 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1454 }
1455 })?;
1456
1457 Ok(create_response_json)
1458 }
1459}
1460
1461#[derive(Clone, Debug, Derivative)]
1463#[derivative(PartialEq)]
1464pub struct ReceiveSwap {
1465 pub(crate) id: String,
1466 pub(crate) preimage: String,
1467 pub(crate) create_response_json: String,
1469 pub(crate) claim_private_key: String,
1470 pub(crate) invoice: String,
1471 pub(crate) bolt12_offer: Option<String>,
1473 pub(crate) payment_hash: Option<String>,
1474 pub(crate) destination_pubkey: Option<String>,
1475 pub(crate) description: Option<String>,
1476 pub(crate) payer_note: Option<String>,
1477 pub(crate) payer_amount_sat: u64,
1479 pub(crate) receiver_amount_sat: u64,
1480 pub(crate) pair_fees_json: String,
1482 pub(crate) claim_fees_sat: u64,
1483 pub(crate) claim_address: Option<String>,
1485 pub(crate) claim_tx_id: Option<String>,
1487 pub(crate) lockup_tx_id: Option<String>,
1489 pub(crate) mrh_address: String,
1491 pub(crate) mrh_tx_id: Option<String>,
1493 pub(crate) created_at: u32,
1496 pub(crate) timeout_block_height: u32,
1497 pub(crate) state: PaymentState,
1498 #[derivative(PartialEq = "ignore")]
1500 pub(crate) metadata: SwapMetadata,
1501}
1502impl ReceiveSwap {
1503 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1504 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1505 }
1506
1507 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1508 Ok(self
1509 .get_swap_script()?
1510 .funding_addrs
1511 .ok_or(anyhow!("No funding address found"))?
1512 .script_pubkey())
1513 }
1514
1515 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1516 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1517 serde_json::from_str(&self.create_response_json).map_err(|e| {
1518 PaymentError::Generic {
1519 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1520 }
1521 })?;
1522
1523 let res = CreateReverseResponse {
1524 id: self.id.clone(),
1525 invoice: Some(self.invoice.clone()),
1526 swap_tree: internal_create_response.swap_tree.clone().into(),
1527 lockup_address: internal_create_response.lockup_address.clone(),
1528 refund_public_key: crate::utils::json_to_pubkey(
1529 &internal_create_response.refund_public_key,
1530 )?,
1531 timeout_block_height: internal_create_response.timeout_block_height,
1532 onchain_amount: internal_create_response.onchain_amount,
1533 blinding_key: internal_create_response.blinding_key.clone(),
1534 };
1535 Ok(res)
1536 }
1537
1538 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1539 let keypair = self.get_claim_keypair()?;
1540 let create_response =
1541 self.get_boltz_create_response()
1542 .map_err(|e| PaymentError::Generic {
1543 err: format!(
1544 "Failed to create swap script for Receive Swap {}: {e:?}",
1545 self.id
1546 ),
1547 })?;
1548 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1549 .map_err(|e| PaymentError::Generic {
1550 err: format!(
1551 "Failed to create swap script for Receive Swap {}: {e:?}",
1552 self.id
1553 ),
1554 })
1555 }
1556
1557 pub(crate) fn from_boltz_struct_to_json(
1558 create_response: &CreateReverseResponse,
1559 expected_swap_id: &str,
1560 expected_invoice: Option<&str>,
1561 ) -> Result<String, PaymentError> {
1562 let internal_create_response =
1563 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1564 create_response,
1565 expected_swap_id,
1566 expected_invoice,
1567 )?;
1568
1569 let create_response_json =
1570 serde_json::to_string(&internal_create_response).map_err(|e| {
1571 PaymentError::Generic {
1572 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1573 }
1574 })?;
1575
1576 Ok(create_response_json)
1577 }
1578}
1579
1580#[derive(Clone, Debug, PartialEq, Serialize)]
1582pub struct RefundableSwap {
1583 pub swap_address: String,
1584 pub timestamp: u32,
1585 pub amount_sat: u64,
1587 pub last_refund_tx_id: Option<String>,
1589}
1590
1591#[derive(Clone, Debug, Derivative)]
1593#[derivative(PartialEq)]
1594pub(crate) struct Bolt12Offer {
1595 pub(crate) id: String,
1597 pub(crate) description: String,
1599 pub(crate) private_key: String,
1601 pub(crate) webhook_url: Option<String>,
1603 pub(crate) created_at: u32,
1605}
1606impl Bolt12Offer {
1607 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1608 utils::decode_keypair(&self.private_key)
1609 }
1610}
1611impl TryFrom<Bolt12Offer> for Offer {
1612 type Error = SdkError;
1613
1614 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1615 Offer::from_str(&val.id)
1616 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1617 }
1618}
1619
1620#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1622#[strum(serialize_all = "lowercase")]
1623pub enum PaymentState {
1624 #[default]
1625 Created = 0,
1626
1627 Pending = 1,
1647
1648 Complete = 2,
1660
1661 Failed = 3,
1669
1670 TimedOut = 4,
1675
1676 Refundable = 5,
1681
1682 RefundPending = 6,
1688
1689 WaitingFeeAcceptance = 7,
1701}
1702
1703impl ToSql for PaymentState {
1704 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1705 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1706 }
1707}
1708impl FromSql for PaymentState {
1709 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1710 match value {
1711 ValueRef::Integer(i) => match i as u8 {
1712 0 => Ok(PaymentState::Created),
1713 1 => Ok(PaymentState::Pending),
1714 2 => Ok(PaymentState::Complete),
1715 3 => Ok(PaymentState::Failed),
1716 4 => Ok(PaymentState::TimedOut),
1717 5 => Ok(PaymentState::Refundable),
1718 6 => Ok(PaymentState::RefundPending),
1719 7 => Ok(PaymentState::WaitingFeeAcceptance),
1720 _ => Err(FromSqlError::OutOfRange(i)),
1721 },
1722 _ => Err(FromSqlError::InvalidType),
1723 }
1724 }
1725}
1726
1727impl PaymentState {
1728 pub(crate) fn is_refundable(&self) -> bool {
1729 matches!(
1730 self,
1731 PaymentState::Refundable
1732 | PaymentState::RefundPending
1733 | PaymentState::WaitingFeeAcceptance
1734 )
1735 }
1736}
1737
1738#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1739#[strum(serialize_all = "lowercase")]
1740pub enum PaymentType {
1741 Receive = 0,
1742 Send = 1,
1743}
1744impl From<Direction> for PaymentType {
1745 fn from(value: Direction) -> Self {
1746 match value {
1747 Direction::Incoming => Self::Receive,
1748 Direction::Outgoing => Self::Send,
1749 }
1750 }
1751}
1752impl ToSql for PaymentType {
1753 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1754 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1755 }
1756}
1757impl FromSql for PaymentType {
1758 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1759 match value {
1760 ValueRef::Integer(i) => match i as u8 {
1761 0 => Ok(PaymentType::Receive),
1762 1 => Ok(PaymentType::Send),
1763 _ => Err(FromSqlError::OutOfRange(i)),
1764 },
1765 _ => Err(FromSqlError::InvalidType),
1766 }
1767 }
1768}
1769
1770#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1771pub enum PaymentStatus {
1772 Pending = 0,
1773 Complete = 1,
1774}
1775impl ToSql for PaymentStatus {
1776 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1777 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1778 }
1779}
1780impl FromSql for PaymentStatus {
1781 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1782 match value {
1783 ValueRef::Integer(i) => match i as u8 {
1784 0 => Ok(PaymentStatus::Pending),
1785 1 => Ok(PaymentStatus::Complete),
1786 _ => Err(FromSqlError::OutOfRange(i)),
1787 },
1788 _ => Err(FromSqlError::InvalidType),
1789 }
1790 }
1791}
1792
1793#[derive(Debug, Clone, Serialize)]
1794pub struct PaymentTxData {
1795 pub tx_id: String,
1797
1798 pub timestamp: Option<u32>,
1800
1801 pub fees_sat: u64,
1803
1804 pub is_confirmed: bool,
1806
1807 pub unblinding_data: Option<String>,
1810}
1811
1812#[derive(Debug, Clone, Serialize)]
1813pub enum PaymentSwapType {
1814 Receive,
1815 Send,
1816 Chain,
1817}
1818
1819#[derive(Debug, Clone, Serialize)]
1820pub struct PaymentSwapData {
1821 pub swap_id: String,
1822
1823 pub swap_type: PaymentSwapType,
1824
1825 pub created_at: u32,
1827
1828 pub expiration_blockheight: u32,
1830
1831 pub preimage: Option<String>,
1832 pub invoice: Option<String>,
1833 pub bolt12_offer: Option<String>,
1834 pub payment_hash: Option<String>,
1835 pub destination_pubkey: Option<String>,
1836 pub description: String,
1837 pub payer_note: Option<String>,
1838
1839 pub payer_amount_sat: u64,
1841
1842 pub receiver_amount_sat: u64,
1844
1845 pub swapper_fees_sat: u64,
1847
1848 pub refund_tx_id: Option<String>,
1849 pub refund_tx_amount_sat: Option<u64>,
1850
1851 pub bitcoin_address: Option<String>,
1854
1855 pub status: PaymentState,
1857}
1858
1859#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1861pub struct LnUrlInfo {
1862 pub ln_address: Option<String>,
1863 pub lnurl_pay_comment: Option<String>,
1864 pub lnurl_pay_domain: Option<String>,
1865 pub lnurl_pay_metadata: Option<String>,
1866 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1867 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1868 pub lnurl_withdraw_endpoint: Option<String>,
1869}
1870
1871#[derive(Debug, Clone, Serialize)]
1875pub struct AssetMetadata {
1876 pub asset_id: String,
1878 pub name: String,
1880 pub ticker: String,
1882 pub precision: u8,
1885 pub fiat_id: Option<String>,
1887}
1888
1889impl AssetMetadata {
1890 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1891 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1892 }
1893
1894 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1895 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1896 }
1897}
1898
1899#[derive(Clone, Debug, PartialEq, Serialize)]
1902pub struct AssetInfo {
1903 pub name: String,
1905 pub ticker: String,
1907 pub amount: f64,
1910 pub fees: Option<f64>,
1913}
1914
1915#[derive(Debug, Clone, PartialEq, Serialize)]
1917#[allow(clippy::large_enum_variant)]
1918pub enum PaymentDetails {
1919 Lightning {
1921 swap_id: String,
1922
1923 description: String,
1925
1926 liquid_expiration_blockheight: u32,
1928
1929 preimage: Option<String>,
1931
1932 invoice: Option<String>,
1936
1937 bolt12_offer: Option<String>,
1938
1939 payment_hash: Option<String>,
1941
1942 destination_pubkey: Option<String>,
1944
1945 lnurl_info: Option<LnUrlInfo>,
1947
1948 bip353_address: Option<String>,
1950
1951 payer_note: Option<String>,
1953
1954 claim_tx_id: Option<String>,
1956
1957 refund_tx_id: Option<String>,
1959
1960 refund_tx_amount_sat: Option<u64>,
1962 },
1963 Liquid {
1965 destination: String,
1967
1968 description: String,
1970
1971 asset_id: String,
1973
1974 asset_info: Option<AssetInfo>,
1976
1977 lnurl_info: Option<LnUrlInfo>,
1979
1980 bip353_address: Option<String>,
1982
1983 payer_note: Option<String>,
1985 },
1986 Bitcoin {
1988 swap_id: String,
1989
1990 bitcoin_address: String,
1992
1993 description: String,
1995
1996 auto_accepted_fees: bool,
2000
2001 liquid_expiration_blockheight: Option<u32>,
2004
2005 bitcoin_expiration_blockheight: Option<u32>,
2008
2009 lockup_tx_id: Option<String>,
2011
2012 claim_tx_id: Option<String>,
2014
2015 refund_tx_id: Option<String>,
2017
2018 refund_tx_amount_sat: Option<u64>,
2020 },
2021}
2022
2023impl PaymentDetails {
2024 pub(crate) fn get_swap_id(&self) -> Option<String> {
2025 match self {
2026 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2027 Some(swap_id.clone())
2028 }
2029 Self::Liquid { .. } => None,
2030 }
2031 }
2032
2033 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2034 match self {
2035 Self::Lightning {
2036 refund_tx_amount_sat,
2037 ..
2038 }
2039 | Self::Bitcoin {
2040 refund_tx_amount_sat,
2041 ..
2042 } => *refund_tx_amount_sat,
2043 Self::Liquid { .. } => None,
2044 }
2045 }
2046
2047 pub(crate) fn get_description(&self) -> Option<String> {
2048 match self {
2049 Self::Lightning { description, .. }
2050 | Self::Bitcoin { description, .. }
2051 | Self::Liquid { description, .. } => Some(description.clone()),
2052 }
2053 }
2054
2055 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2056 match self {
2057 Self::Liquid { asset_id, .. } => {
2058 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2059 }
2060 _ => true,
2061 }
2062 }
2063}
2064
2065#[derive(Debug, Clone, PartialEq, Serialize)]
2069pub struct Payment {
2070 pub destination: Option<String>,
2073
2074 pub tx_id: Option<String>,
2075
2076 pub unblinding_data: Option<String>,
2079
2080 pub timestamp: u32,
2086
2087 pub amount_sat: u64,
2091
2092 pub fees_sat: u64,
2106
2107 pub swapper_fees_sat: Option<u64>,
2110
2111 pub payment_type: PaymentType,
2113
2114 pub status: PaymentState,
2120
2121 pub details: PaymentDetails,
2124}
2125impl Payment {
2126 pub(crate) fn from_pending_swap(
2127 swap: PaymentSwapData,
2128 payment_type: PaymentType,
2129 payment_details: PaymentDetails,
2130 ) -> Payment {
2131 let amount_sat = match payment_type {
2132 PaymentType::Receive => swap.receiver_amount_sat,
2133 PaymentType::Send => swap.payer_amount_sat,
2134 };
2135
2136 Payment {
2137 destination: swap.invoice.clone(),
2138 tx_id: None,
2139 unblinding_data: None,
2140 timestamp: swap.created_at,
2141 amount_sat,
2142 fees_sat: swap
2143 .payer_amount_sat
2144 .saturating_sub(swap.receiver_amount_sat),
2145 swapper_fees_sat: Some(swap.swapper_fees_sat),
2146 payment_type,
2147 status: swap.status,
2148 details: payment_details,
2149 }
2150 }
2151
2152 pub(crate) fn from_tx_data(
2153 tx: PaymentTxData,
2154 balance: PaymentTxBalance,
2155 swap: Option<PaymentSwapData>,
2156 details: PaymentDetails,
2157 ) -> Payment {
2158 let (amount_sat, fees_sat) = match swap.as_ref() {
2159 Some(s) => match balance.payment_type {
2160 PaymentType::Receive => (
2164 balance.amount,
2165 s.payer_amount_sat.saturating_sub(balance.amount),
2166 ),
2167 PaymentType::Send => (
2168 s.receiver_amount_sat,
2169 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2170 ),
2171 },
2172 None => {
2173 let (amount_sat, fees_sat) = match balance.payment_type {
2174 PaymentType::Receive => (balance.amount, 0),
2175 PaymentType::Send => (balance.amount, tx.fees_sat),
2176 };
2177 match details {
2180 PaymentDetails::Liquid {
2181 asset_info: Some(ref asset_info),
2182 ..
2183 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2184 _ => (amount_sat, fees_sat),
2185 }
2186 }
2187 };
2188 Payment {
2189 tx_id: Some(tx.tx_id),
2190 unblinding_data: tx.unblinding_data,
2191 destination: match &swap {
2195 Some(PaymentSwapData {
2196 swap_type: PaymentSwapType::Receive,
2197 invoice,
2198 ..
2199 }) => invoice.clone(),
2200 Some(PaymentSwapData {
2201 swap_type: PaymentSwapType::Send,
2202 invoice,
2203 bolt12_offer,
2204 ..
2205 }) => bolt12_offer.clone().or(invoice.clone()),
2206 Some(PaymentSwapData {
2207 swap_type: PaymentSwapType::Chain,
2208 bitcoin_address,
2209 ..
2210 }) => bitcoin_address.clone(),
2211 _ => match &details {
2212 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2213 _ => None,
2214 },
2215 },
2216 timestamp: tx
2217 .timestamp
2218 .or(swap.as_ref().map(|s| s.created_at))
2219 .unwrap_or(utils::now()),
2220 amount_sat,
2221 fees_sat,
2222 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2223 payment_type: balance.payment_type,
2224 status: match &swap {
2225 Some(swap) => swap.status,
2226 None => match tx.is_confirmed {
2227 true => PaymentState::Complete,
2228 false => PaymentState::Pending,
2229 },
2230 },
2231 details,
2232 }
2233 }
2234
2235 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2236 match self.details.clone() {
2237 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2238 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2239 PaymentDetails::Liquid { .. } => None,
2240 }
2241 .flatten()
2242 }
2243}
2244
2245#[derive(Deserialize, Serialize, Clone, Debug)]
2247#[serde(rename_all = "camelCase")]
2248pub struct RecommendedFees {
2249 pub fastest_fee: u64,
2250 pub half_hour_fee: u64,
2251 pub hour_fee: u64,
2252 pub economy_fee: u64,
2253 pub minimum_fee: u64,
2254}
2255
2256#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2258pub enum BuyBitcoinProvider {
2259 #[strum(serialize = "moonpay")]
2260 Moonpay,
2261}
2262
2263#[derive(Debug, Serialize)]
2265pub struct PrepareBuyBitcoinRequest {
2266 pub provider: BuyBitcoinProvider,
2267 pub amount_sat: u64,
2268}
2269
2270#[derive(Clone, Debug, Serialize)]
2272pub struct PrepareBuyBitcoinResponse {
2273 pub provider: BuyBitcoinProvider,
2274 pub amount_sat: u64,
2275 pub fees_sat: u64,
2276}
2277
2278#[derive(Clone, Debug, Serialize)]
2280pub struct BuyBitcoinRequest {
2281 pub prepare_response: PrepareBuyBitcoinResponse,
2282 pub redirect_url: Option<String>,
2286}
2287
2288#[derive(Clone, Debug)]
2290pub struct LogEntry {
2291 pub line: String,
2292 pub level: String,
2293}
2294
2295#[derive(Clone, Debug, Serialize, Deserialize)]
2296struct InternalLeaf {
2297 pub output: String,
2298 pub version: u8,
2299}
2300impl From<InternalLeaf> for Leaf {
2301 fn from(value: InternalLeaf) -> Self {
2302 Leaf {
2303 output: value.output,
2304 version: value.version,
2305 }
2306 }
2307}
2308impl From<Leaf> for InternalLeaf {
2309 fn from(value: Leaf) -> Self {
2310 InternalLeaf {
2311 output: value.output,
2312 version: value.version,
2313 }
2314 }
2315}
2316
2317#[derive(Clone, Debug, Serialize, Deserialize)]
2318pub(super) struct InternalSwapTree {
2319 claim_leaf: InternalLeaf,
2320 refund_leaf: InternalLeaf,
2321}
2322impl From<InternalSwapTree> for SwapTree {
2323 fn from(value: InternalSwapTree) -> Self {
2324 SwapTree {
2325 claim_leaf: value.claim_leaf.into(),
2326 refund_leaf: value.refund_leaf.into(),
2327 }
2328 }
2329}
2330impl From<SwapTree> for InternalSwapTree {
2331 fn from(value: SwapTree) -> Self {
2332 InternalSwapTree {
2333 claim_leaf: value.claim_leaf.into(),
2334 refund_leaf: value.refund_leaf.into(),
2335 }
2336 }
2337}
2338
2339#[derive(Debug, Serialize)]
2341pub struct PrepareLnUrlPayRequest {
2342 pub data: LnUrlPayRequestData,
2344 pub amount: PayAmount,
2346 pub bip353_address: Option<String>,
2349 pub comment: Option<String>,
2352 pub validate_success_action_url: Option<bool>,
2355}
2356
2357#[derive(Debug, Serialize)]
2359pub struct PrepareLnUrlPayResponse {
2360 pub destination: SendDestination,
2362 pub fees_sat: u64,
2364 pub data: LnUrlPayRequestData,
2366 pub amount: PayAmount,
2368 pub comment: Option<String>,
2371 pub success_action: Option<SuccessAction>,
2374}
2375
2376#[derive(Debug, Serialize)]
2378pub struct LnUrlPayRequest {
2379 pub prepare_response: PrepareLnUrlPayResponse,
2381}
2382
2383#[derive(Serialize)]
2395#[allow(clippy::large_enum_variant)]
2396pub enum LnUrlPayResult {
2397 EndpointSuccess { data: LnUrlPaySuccessData },
2398 EndpointError { data: LnUrlErrorData },
2399 PayError { data: LnUrlPayErrorData },
2400}
2401
2402#[derive(Serialize)]
2403pub struct LnUrlPaySuccessData {
2404 pub payment: Payment,
2405 pub success_action: Option<SuccessActionProcessed>,
2406}
2407
2408#[derive(Debug, Clone)]
2409pub enum Transaction {
2410 Liquid(boltz_client::elements::Transaction),
2411 Bitcoin(boltz_client::bitcoin::Transaction),
2412}
2413
2414impl Transaction {
2415 pub(crate) fn txid(&self) -> String {
2416 match self {
2417 Transaction::Liquid(tx) => tx.txid().to_hex(),
2418 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2419 }
2420 }
2421}
2422
2423#[derive(Debug, Clone)]
2424pub enum Utxo {
2425 Liquid(
2426 Box<(
2427 boltz_client::elements::OutPoint,
2428 boltz_client::elements::TxOut,
2429 )>,
2430 ),
2431 Bitcoin(
2432 (
2433 boltz_client::bitcoin::OutPoint,
2434 boltz_client::bitcoin::TxOut,
2435 ),
2436 ),
2437}
2438
2439impl Utxo {
2440 pub(crate) fn as_bitcoin(
2441 &self,
2442 ) -> Option<&(
2443 boltz_client::bitcoin::OutPoint,
2444 boltz_client::bitcoin::TxOut,
2445 )> {
2446 match self {
2447 Utxo::Liquid(_) => None,
2448 Utxo::Bitcoin(utxo) => Some(utxo),
2449 }
2450 }
2451
2452 pub(crate) fn as_liquid(
2453 &self,
2454 ) -> Option<
2455 Box<(
2456 boltz_client::elements::OutPoint,
2457 boltz_client::elements::TxOut,
2458 )>,
2459 > {
2460 match self {
2461 Utxo::Bitcoin(_) => None,
2462 Utxo::Liquid(utxo) => Some(utxo.clone()),
2463 }
2464 }
2465}
2466
2467#[derive(Debug, Clone)]
2469pub struct FetchPaymentProposedFeesRequest {
2470 pub swap_id: String,
2471}
2472
2473#[derive(Debug, Clone, Serialize)]
2475pub struct FetchPaymentProposedFeesResponse {
2476 pub swap_id: String,
2477 pub fees_sat: u64,
2478 pub payer_amount_sat: u64,
2480 pub receiver_amount_sat: u64,
2482}
2483
2484#[derive(Debug, Clone)]
2486pub struct AcceptPaymentProposedFeesRequest {
2487 pub response: FetchPaymentProposedFeesResponse,
2488}
2489
2490#[derive(Clone, Debug)]
2491pub struct History<T> {
2492 pub txid: T,
2493 pub height: i32,
2498}
2499pub(crate) type LBtcHistory = History<elements::Txid>;
2500pub(crate) type BtcHistory = History<bitcoin::Txid>;
2501
2502impl<T> History<T> {
2503 pub(crate) fn confirmed(&self) -> bool {
2504 self.height > 0
2505 }
2506}
2507#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2508impl From<electrum_client::GetHistoryRes> for BtcHistory {
2509 fn from(value: electrum_client::GetHistoryRes) -> Self {
2510 Self {
2511 txid: value.tx_hash,
2512 height: value.height,
2513 }
2514 }
2515}
2516impl From<lwk_wollet::History> for LBtcHistory {
2517 fn from(value: lwk_wollet::History) -> Self {
2518 Self::from(&value)
2519 }
2520}
2521impl From<&lwk_wollet::History> for LBtcHistory {
2522 fn from(value: &lwk_wollet::History) -> Self {
2523 Self {
2524 txid: value.txid,
2525 height: value.height,
2526 }
2527 }
2528}
2529pub(crate) type BtcScript = bitcoin::ScriptBuf;
2530pub(crate) type LBtcScript = elements::Script;
2531
2532#[derive(Clone, Debug)]
2533pub struct BtcScriptBalance {
2534 pub confirmed: u64,
2536 pub unconfirmed: i64,
2540}
2541#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2542impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2543 fn from(val: electrum_client::GetBalanceRes) -> Self {
2544 Self {
2545 confirmed: val.confirmed,
2546 unconfirmed: val.unconfirmed,
2547 }
2548 }
2549}
2550
2551pub(crate) struct GetSyncContextRequest {
2552 pub partial_sync: Option<bool>,
2553 pub last_liquid_tip: u32,
2554 pub last_bitcoin_tip: u32,
2555}
2556
2557pub(crate) struct SyncContext {
2558 pub maybe_liquid_tip: Option<u32>,
2559 pub maybe_bitcoin_tip: Option<u32>,
2560 pub recoverable_swaps: Vec<Swap>,
2561 pub is_new_liquid_block: bool,
2562 pub is_new_bitcoin_block: bool,
2563}
2564
2565pub(crate) struct TaskHandle {
2566 pub name: String,
2567 pub handle: tokio::task::JoinHandle<()>,
2568}
2569
2570#[macro_export]
2571macro_rules! get_updated_fields {
2572 ($($var:ident),* $(,)?) => {{
2573 let mut options = Vec::new();
2574 $(
2575 if $var.is_some() {
2576 options.push(stringify!($var).to_string());
2577 }
2578 )*
2579 match options.len() > 0 {
2580 true => Some(options),
2581 false => None,
2582 }
2583 }};
2584}