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 pub liquid_expiration_blockheight: Option<u32>,
674 pub bitcoin_expiration_blockheight: Option<u32>,
676}
677
678#[derive(Debug, Serialize)]
680pub struct CreateBolt12InvoiceRequest {
681 pub offer: String,
683 pub invoice_request: String,
685}
686
687#[derive(Debug, Serialize, Clone)]
689pub struct CreateBolt12InvoiceResponse {
690 pub invoice: String,
692}
693
694#[derive(Debug, Serialize)]
696pub struct Limits {
697 pub min_sat: u64,
698 pub max_sat: u64,
699 pub max_zero_conf_sat: u64,
700}
701
702#[derive(Debug, Serialize)]
704pub struct LightningPaymentLimitsResponse {
705 pub send: Limits,
707 pub receive: Limits,
709}
710
711#[derive(Debug, Serialize)]
713pub struct OnchainPaymentLimitsResponse {
714 pub send: Limits,
716 pub receive: Limits,
718}
719
720#[derive(Debug, Serialize, Clone)]
722pub struct PrepareSendRequest {
723 pub destination: String,
726 pub amount: Option<PayAmount>,
729}
730
731#[derive(Clone, Debug, Serialize)]
733pub enum SendDestination {
734 LiquidAddress {
735 address_data: liquid::LiquidAddressData,
736 bip353_address: Option<String>,
738 },
739 Bolt11 {
740 invoice: LNInvoice,
741 bip353_address: Option<String>,
743 },
744 Bolt12 {
745 offer: LNOffer,
746 receiver_amount_sat: u64,
747 bip353_address: Option<String>,
749 },
750}
751
752#[derive(Debug, Serialize, Clone)]
754pub struct PrepareSendResponse {
755 pub destination: SendDestination,
756 pub amount: Option<PayAmount>,
758 pub fees_sat: Option<u64>,
761 pub estimated_asset_fees: Option<f64>,
765 pub exchange_amount_sat: Option<u64>,
768}
769
770#[derive(Debug, Serialize)]
772pub struct SendPaymentRequest {
773 pub prepare_response: PrepareSendResponse,
774 pub use_asset_fees: Option<bool>,
776 pub payer_note: Option<String>,
778}
779
780#[derive(Debug, Serialize)]
782pub struct SendPaymentResponse {
783 pub payment: Payment,
784}
785
786pub(crate) struct SendPaymentViaSwapRequest {
787 pub(crate) invoice: String,
788 pub(crate) bolt12_offer: Option<String>,
789 pub(crate) payment_hash: String,
790 pub(crate) description: Option<String>,
791 pub(crate) receiver_amount_sat: u64,
792 pub(crate) fees_sat: u64,
793}
794
795pub(crate) struct PayLiquidRequest {
796 pub address_data: LiquidAddressData,
797 pub to_asset: String,
798 pub receiver_amount_sat: u64,
799 pub asset_pay_fees: bool,
800 pub fees_sat: Option<u64>,
801}
802
803pub(crate) struct PaySideSwapRequest {
804 pub address_data: LiquidAddressData,
805 pub to_asset: String,
806 pub receiver_amount_sat: u64,
807 pub fees_sat: u64,
808 pub amount: Option<PayAmount>,
809}
810
811#[derive(Debug, Serialize, Clone)]
813pub enum PayAmount {
814 Bitcoin { receiver_amount_sat: u64 },
816
817 Asset {
819 to_asset: String,
821 receiver_amount: f64,
822 estimate_asset_fees: Option<bool>,
823 from_asset: Option<String>,
826 },
827
828 Drain,
830}
831
832impl PayAmount {
833 pub(crate) fn is_sideswap_payment(&self) -> bool {
834 match self {
835 PayAmount::Asset {
836 to_asset,
837 from_asset,
838 ..
839 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
840 _ => false,
841 }
842 }
843}
844
845#[derive(Debug, Serialize, Clone)]
847pub struct PreparePayOnchainRequest {
848 pub amount: PayAmount,
850 pub fee_rate_sat_per_vbyte: Option<u32>,
852}
853
854#[derive(Debug, Serialize, Clone)]
856pub struct PreparePayOnchainResponse {
857 pub receiver_amount_sat: u64,
858 pub claim_fees_sat: u64,
859 pub total_fees_sat: u64,
860}
861
862#[derive(Debug, Serialize)]
864pub struct PayOnchainRequest {
865 pub address: String,
866 pub prepare_response: PreparePayOnchainResponse,
867}
868
869#[derive(Debug, Serialize)]
871pub struct PrepareRefundRequest {
872 pub swap_address: String,
874 pub refund_address: String,
876 pub fee_rate_sat_per_vbyte: u32,
878}
879
880#[derive(Debug, Serialize)]
882pub struct PrepareRefundResponse {
883 pub tx_vsize: u32,
884 pub tx_fee_sat: u64,
885 pub last_refund_tx_id: Option<String>,
887}
888
889#[derive(Debug, Serialize)]
891pub struct RefundRequest {
892 pub swap_address: String,
894 pub refund_address: String,
896 pub fee_rate_sat_per_vbyte: u32,
898}
899
900#[derive(Debug, Serialize)]
902pub struct RefundResponse {
903 pub refund_tx_id: String,
904}
905
906#[derive(Clone, Debug, Default, Serialize, Deserialize)]
908pub struct AssetBalance {
909 pub asset_id: String,
910 pub balance_sat: u64,
911 pub name: Option<String>,
912 pub ticker: Option<String>,
913 pub balance: Option<f64>,
914}
915
916#[derive(Debug, Serialize, Deserialize, Default)]
917pub struct BlockchainInfo {
918 pub liquid_tip: u32,
919 pub bitcoin_tip: u32,
920}
921
922#[derive(Copy, Clone)]
923pub(crate) struct ChainTips {
924 pub liquid_tip: u32,
925 pub bitcoin_tip: Option<u32>,
926}
927
928#[derive(Debug, Serialize, Deserialize)]
929pub struct WalletInfo {
930 pub balance_sat: u64,
932 pub pending_send_sat: u64,
934 pub pending_receive_sat: u64,
936 pub fingerprint: String,
938 pub pubkey: String,
940 #[serde(default)]
942 pub asset_balances: Vec<AssetBalance>,
943}
944
945impl WalletInfo {
946 pub(crate) fn validate_sufficient_funds(
947 &self,
948 network: LiquidNetwork,
949 amount_sat: u64,
950 fees_sat: Option<u64>,
951 asset_id: &str,
952 ) -> Result<(), PaymentError> {
953 let fees_sat = fees_sat.unwrap_or(0);
954 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
955 ensure_sdk!(
956 amount_sat + fees_sat <= self.balance_sat,
957 PaymentError::InsufficientFunds
958 );
959 } else {
960 match self
961 .asset_balances
962 .iter()
963 .find(|ab| ab.asset_id.eq(asset_id))
964 {
965 Some(asset_balance) => ensure_sdk!(
966 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
967 PaymentError::InsufficientFunds
968 ),
969 None => return Err(PaymentError::InsufficientFunds),
970 }
971 }
972 Ok(())
973 }
974}
975
976#[derive(Debug, Serialize, Deserialize)]
978pub struct GetInfoResponse {
979 pub wallet_info: WalletInfo,
981 #[serde(default)]
983 pub blockchain_info: BlockchainInfo,
984}
985
986#[derive(Clone, Debug, PartialEq)]
988pub struct SignMessageRequest {
989 pub message: String,
990}
991
992#[derive(Clone, Debug, PartialEq)]
994pub struct SignMessageResponse {
995 pub signature: String,
996}
997
998#[derive(Clone, Debug, PartialEq)]
1000pub struct CheckMessageRequest {
1001 pub message: String,
1003 pub pubkey: String,
1005 pub signature: String,
1007}
1008
1009#[derive(Clone, Debug, PartialEq)]
1011pub struct CheckMessageResponse {
1012 pub is_valid: bool,
1015}
1016
1017#[derive(Debug, Serialize)]
1019pub struct BackupRequest {
1020 pub backup_path: Option<String>,
1027}
1028
1029#[derive(Debug, Serialize)]
1031pub struct RestoreRequest {
1032 pub backup_path: Option<String>,
1033}
1034
1035#[derive(Default)]
1037pub struct ListPaymentsRequest {
1038 pub filters: Option<Vec<PaymentType>>,
1039 pub states: Option<Vec<PaymentState>>,
1040 pub from_timestamp: Option<i64>,
1042 pub to_timestamp: Option<i64>,
1044 pub offset: Option<u32>,
1045 pub limit: Option<u32>,
1046 pub details: Option<ListPaymentDetails>,
1047 pub sort_ascending: Option<bool>,
1048}
1049
1050#[derive(Debug, Serialize)]
1052pub enum ListPaymentDetails {
1053 Liquid {
1055 asset_id: Option<String>,
1057 destination: Option<String>,
1059 },
1060
1061 Bitcoin {
1063 address: Option<String>,
1065 },
1066}
1067
1068#[derive(Debug, Serialize)]
1070pub enum GetPaymentRequest {
1071 PaymentHash { payment_hash: String },
1073 SwapId { swap_id: String },
1075}
1076
1077#[sdk_macros::async_trait]
1079pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1080 async fn on_bitcoin_block(&self, height: u32);
1081 async fn on_liquid_block(&self, height: u32);
1082}
1083
1084#[derive(Clone, Debug)]
1086pub enum Swap {
1087 Chain(ChainSwap),
1088 Send(SendSwap),
1089 Receive(ReceiveSwap),
1090}
1091impl Swap {
1092 pub(crate) fn id(&self) -> String {
1093 match &self {
1094 Swap::Chain(ChainSwap { id, .. })
1095 | Swap::Send(SendSwap { id, .. })
1096 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1097 }
1098 }
1099
1100 pub(crate) fn version(&self) -> u64 {
1101 match self {
1102 Swap::Chain(ChainSwap { metadata, .. })
1103 | Swap::Send(SendSwap { metadata, .. })
1104 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1105 }
1106 }
1107
1108 pub(crate) fn set_version(&mut self, version: u64) {
1109 match self {
1110 Swap::Chain(chain_swap) => {
1111 chain_swap.metadata.version = version;
1112 }
1113 Swap::Send(send_swap) => {
1114 send_swap.metadata.version = version;
1115 }
1116 Swap::Receive(receive_swap) => {
1117 receive_swap.metadata.version = version;
1118 }
1119 }
1120 }
1121
1122 pub(crate) fn last_updated_at(&self) -> u32 {
1123 match self {
1124 Swap::Chain(ChainSwap { metadata, .. })
1125 | Swap::Send(SendSwap { metadata, .. })
1126 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1127 }
1128 }
1129}
1130impl From<ChainSwap> for Swap {
1131 fn from(swap: ChainSwap) -> Self {
1132 Self::Chain(swap)
1133 }
1134}
1135impl From<SendSwap> for Swap {
1136 fn from(swap: SendSwap) -> Self {
1137 Self::Send(swap)
1138 }
1139}
1140impl From<ReceiveSwap> for Swap {
1141 fn from(swap: ReceiveSwap) -> Self {
1142 Self::Receive(swap)
1143 }
1144}
1145
1146#[derive(Clone, Debug)]
1147pub(crate) enum SwapScriptV2 {
1148 Bitcoin(BtcSwapScript),
1149 Liquid(LBtcSwapScript),
1150}
1151impl SwapScriptV2 {
1152 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1153 match self {
1154 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1155 _ => Err(anyhow!("Invalid chain")),
1156 }
1157 }
1158
1159 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1160 match self {
1161 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1162 _ => Err(anyhow!("Invalid chain")),
1163 }
1164 }
1165}
1166
1167#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1168pub enum Direction {
1169 Incoming = 0,
1170 Outgoing = 1,
1171}
1172impl ToSql for Direction {
1173 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1174 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1175 }
1176}
1177impl FromSql for Direction {
1178 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1179 match value {
1180 ValueRef::Integer(i) => match i as u8 {
1181 0 => Ok(Direction::Incoming),
1182 1 => Ok(Direction::Outgoing),
1183 _ => Err(FromSqlError::OutOfRange(i)),
1184 },
1185 _ => Err(FromSqlError::InvalidType),
1186 }
1187 }
1188}
1189
1190#[derive(Clone, Debug, Default)]
1191pub(crate) struct SwapMetadata {
1192 pub(crate) version: u64,
1194 pub(crate) last_updated_at: u32,
1195 pub(crate) is_local: bool,
1196}
1197
1198#[derive(Clone, Debug, Derivative)]
1202#[derivative(PartialEq)]
1203pub struct ChainSwap {
1204 pub(crate) id: String,
1205 pub(crate) direction: Direction,
1206 pub(crate) claim_address: Option<String>,
1209 pub(crate) lockup_address: String,
1210 pub(crate) refund_address: Option<String>,
1212 pub(crate) timeout_block_height: u32,
1214 pub(crate) claim_timeout_block_height: u32,
1216 pub(crate) preimage: String,
1217 pub(crate) description: Option<String>,
1218 pub(crate) payer_amount_sat: u64,
1220 pub(crate) actual_payer_amount_sat: Option<u64>,
1223 pub(crate) receiver_amount_sat: u64,
1225 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1227 pub(crate) claim_fees_sat: u64,
1228 pub(crate) pair_fees_json: String,
1230 pub(crate) accept_zero_conf: bool,
1231 pub(crate) create_response_json: String,
1233 pub(crate) server_lockup_tx_id: Option<String>,
1235 pub(crate) user_lockup_tx_id: Option<String>,
1237 pub(crate) claim_tx_id: Option<String>,
1239 pub(crate) refund_tx_id: Option<String>,
1241 pub(crate) created_at: u32,
1242 pub(crate) state: PaymentState,
1243 pub(crate) claim_private_key: String,
1244 pub(crate) refund_private_key: String,
1245 pub(crate) auto_accepted_fees: bool,
1246 #[derivative(PartialEq = "ignore")]
1248 pub(crate) metadata: SwapMetadata,
1249}
1250impl ChainSwap {
1251 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1252 utils::decode_keypair(&self.claim_private_key)
1253 }
1254
1255 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1256 utils::decode_keypair(&self.refund_private_key)
1257 }
1258
1259 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1260 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1261 serde_json::from_str(&self.create_response_json).map_err(|e| {
1262 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1263 })?;
1264
1265 Ok(CreateChainResponse {
1266 id: self.id.clone(),
1267 claim_details: internal_create_response.claim_details,
1268 lockup_details: internal_create_response.lockup_details,
1269 })
1270 }
1271
1272 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1273 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1274 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1275
1276 Ok(pair)
1277 }
1278
1279 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1280 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1281 let our_pubkey = self.get_claim_keypair()?.public_key();
1282 let swap_script = match self.direction {
1283 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1284 Side::Claim,
1285 chain_swap_details,
1286 our_pubkey.into(),
1287 )?),
1288 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1289 Side::Claim,
1290 chain_swap_details,
1291 our_pubkey.into(),
1292 )?),
1293 };
1294 Ok(swap_script)
1295 }
1296
1297 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1298 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1299 let our_pubkey = self.get_refund_keypair()?.public_key();
1300 let swap_script = match self.direction {
1301 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1302 Side::Lockup,
1303 chain_swap_details,
1304 our_pubkey.into(),
1305 )?),
1306 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1307 Side::Lockup,
1308 chain_swap_details,
1309 our_pubkey.into(),
1310 )?),
1311 };
1312 Ok(swap_script)
1313 }
1314
1315 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1317 &self,
1318 network: LiquidNetwork,
1319 ) -> SdkResult<ScriptBuf> {
1320 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1321 let script_pubkey = swap_script
1322 .to_address(network.as_bitcoin_chain())
1323 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1324 .script_pubkey();
1325 Ok(script_pubkey)
1326 }
1327
1328 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1329 RefundableSwap {
1330 swap_address: self.lockup_address.clone(),
1331 timestamp: self.created_at,
1332 amount_sat,
1333 last_refund_tx_id: self.refund_tx_id.clone(),
1334 }
1335 }
1336
1337 pub(crate) fn from_boltz_struct_to_json(
1338 create_response: &CreateChainResponse,
1339 expected_swap_id: &str,
1340 ) -> Result<String, PaymentError> {
1341 let internal_create_response =
1342 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1343 create_response,
1344 expected_swap_id,
1345 )?;
1346
1347 let create_response_json =
1348 serde_json::to_string(&internal_create_response).map_err(|e| {
1349 PaymentError::Generic {
1350 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1351 }
1352 })?;
1353
1354 Ok(create_response_json)
1355 }
1356
1357 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1358 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1359 }
1360}
1361
1362#[derive(Clone, Debug, Default)]
1363pub(crate) struct ChainSwapUpdate {
1364 pub(crate) swap_id: String,
1365 pub(crate) to_state: PaymentState,
1366 pub(crate) server_lockup_tx_id: Option<String>,
1367 pub(crate) user_lockup_tx_id: Option<String>,
1368 pub(crate) claim_address: Option<String>,
1369 pub(crate) claim_tx_id: Option<String>,
1370 pub(crate) refund_tx_id: Option<String>,
1371}
1372
1373#[derive(Clone, Debug, Derivative)]
1375#[derivative(PartialEq)]
1376pub struct SendSwap {
1377 pub(crate) id: String,
1378 pub(crate) invoice: String,
1380 pub(crate) bolt12_offer: Option<String>,
1382 pub(crate) payment_hash: Option<String>,
1383 pub(crate) destination_pubkey: Option<String>,
1384 pub(crate) description: Option<String>,
1385 pub(crate) preimage: Option<String>,
1386 pub(crate) payer_amount_sat: u64,
1387 pub(crate) receiver_amount_sat: u64,
1388 pub(crate) pair_fees_json: String,
1390 pub(crate) create_response_json: String,
1392 pub(crate) lockup_tx_id: Option<String>,
1394 pub(crate) refund_address: Option<String>,
1396 pub(crate) refund_tx_id: Option<String>,
1398 pub(crate) created_at: u32,
1399 pub(crate) timeout_block_height: u64,
1400 pub(crate) state: PaymentState,
1401 pub(crate) refund_private_key: String,
1402 #[derivative(PartialEq = "ignore")]
1404 pub(crate) metadata: SwapMetadata,
1405}
1406impl SendSwap {
1407 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1408 utils::decode_keypair(&self.refund_private_key)
1409 }
1410
1411 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1412 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1413 serde_json::from_str(&self.create_response_json).map_err(|e| {
1414 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1415 })?;
1416
1417 let res = CreateSubmarineResponse {
1418 id: self.id.clone(),
1419 accept_zero_conf: internal_create_response.accept_zero_conf,
1420 address: internal_create_response.address.clone(),
1421 bip21: internal_create_response.bip21.clone(),
1422 claim_public_key: crate::utils::json_to_pubkey(
1423 &internal_create_response.claim_public_key,
1424 )?,
1425 expected_amount: internal_create_response.expected_amount,
1426 referral_id: internal_create_response.referral_id,
1427 swap_tree: internal_create_response.swap_tree.clone().into(),
1428 timeout_block_height: internal_create_response.timeout_block_height,
1429 blinding_key: internal_create_response.blinding_key.clone(),
1430 };
1431 Ok(res)
1432 }
1433
1434 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1435 LBtcSwapScript::submarine_from_swap_resp(
1436 &self.get_boltz_create_response()?,
1437 self.get_refund_keypair()?.public_key().into(),
1438 )
1439 .map_err(|e| {
1440 SdkError::generic(format!(
1441 "Failed to create swap script for Send Swap {}: {e:?}",
1442 self.id
1443 ))
1444 })
1445 }
1446
1447 pub(crate) fn from_boltz_struct_to_json(
1448 create_response: &CreateSubmarineResponse,
1449 expected_swap_id: &str,
1450 ) -> Result<String, PaymentError> {
1451 let internal_create_response =
1452 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1453 create_response,
1454 expected_swap_id,
1455 )?;
1456
1457 let create_response_json =
1458 serde_json::to_string(&internal_create_response).map_err(|e| {
1459 PaymentError::Generic {
1460 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1461 }
1462 })?;
1463
1464 Ok(create_response_json)
1465 }
1466}
1467
1468#[derive(Clone, Debug, Derivative)]
1470#[derivative(PartialEq)]
1471pub struct ReceiveSwap {
1472 pub(crate) id: String,
1473 pub(crate) preimage: String,
1474 pub(crate) create_response_json: String,
1476 pub(crate) claim_private_key: String,
1477 pub(crate) invoice: String,
1478 pub(crate) bolt12_offer: Option<String>,
1480 pub(crate) payment_hash: Option<String>,
1481 pub(crate) destination_pubkey: Option<String>,
1482 pub(crate) description: Option<String>,
1483 pub(crate) payer_note: Option<String>,
1484 pub(crate) payer_amount_sat: u64,
1486 pub(crate) receiver_amount_sat: u64,
1487 pub(crate) pair_fees_json: String,
1489 pub(crate) claim_fees_sat: u64,
1490 pub(crate) claim_address: Option<String>,
1492 pub(crate) claim_tx_id: Option<String>,
1494 pub(crate) lockup_tx_id: Option<String>,
1496 pub(crate) mrh_address: String,
1498 pub(crate) mrh_tx_id: Option<String>,
1500 pub(crate) created_at: u32,
1503 pub(crate) timeout_block_height: u32,
1504 pub(crate) state: PaymentState,
1505 #[derivative(PartialEq = "ignore")]
1507 pub(crate) metadata: SwapMetadata,
1508}
1509impl ReceiveSwap {
1510 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1511 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1512 }
1513
1514 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1515 Ok(self
1516 .get_swap_script()?
1517 .funding_addrs
1518 .ok_or(anyhow!("No funding address found"))?
1519 .script_pubkey())
1520 }
1521
1522 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1523 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1524 serde_json::from_str(&self.create_response_json).map_err(|e| {
1525 PaymentError::Generic {
1526 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1527 }
1528 })?;
1529
1530 let res = CreateReverseResponse {
1531 id: self.id.clone(),
1532 invoice: Some(self.invoice.clone()),
1533 swap_tree: internal_create_response.swap_tree.clone().into(),
1534 lockup_address: internal_create_response.lockup_address.clone(),
1535 refund_public_key: crate::utils::json_to_pubkey(
1536 &internal_create_response.refund_public_key,
1537 )?,
1538 timeout_block_height: internal_create_response.timeout_block_height,
1539 onchain_amount: internal_create_response.onchain_amount,
1540 blinding_key: internal_create_response.blinding_key.clone(),
1541 };
1542 Ok(res)
1543 }
1544
1545 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1546 let keypair = self.get_claim_keypair()?;
1547 let create_response =
1548 self.get_boltz_create_response()
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 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1556 .map_err(|e| PaymentError::Generic {
1557 err: format!(
1558 "Failed to create swap script for Receive Swap {}: {e:?}",
1559 self.id
1560 ),
1561 })
1562 }
1563
1564 pub(crate) fn from_boltz_struct_to_json(
1565 create_response: &CreateReverseResponse,
1566 expected_swap_id: &str,
1567 expected_invoice: Option<&str>,
1568 ) -> Result<String, PaymentError> {
1569 let internal_create_response =
1570 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1571 create_response,
1572 expected_swap_id,
1573 expected_invoice,
1574 )?;
1575
1576 let create_response_json =
1577 serde_json::to_string(&internal_create_response).map_err(|e| {
1578 PaymentError::Generic {
1579 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1580 }
1581 })?;
1582
1583 Ok(create_response_json)
1584 }
1585}
1586
1587#[derive(Clone, Debug, PartialEq, Serialize)]
1589pub struct RefundableSwap {
1590 pub swap_address: String,
1591 pub timestamp: u32,
1592 pub amount_sat: u64,
1594 pub last_refund_tx_id: Option<String>,
1596}
1597
1598#[derive(Clone, Debug, Derivative)]
1600#[derivative(PartialEq)]
1601pub(crate) struct Bolt12Offer {
1602 pub(crate) id: String,
1604 pub(crate) description: String,
1606 pub(crate) private_key: String,
1608 pub(crate) webhook_url: Option<String>,
1610 pub(crate) created_at: u32,
1612}
1613impl Bolt12Offer {
1614 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1615 utils::decode_keypair(&self.private_key)
1616 }
1617}
1618impl TryFrom<Bolt12Offer> for Offer {
1619 type Error = SdkError;
1620
1621 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1622 Offer::from_str(&val.id)
1623 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1624 }
1625}
1626
1627#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1629#[strum(serialize_all = "lowercase")]
1630pub enum PaymentState {
1631 #[default]
1632 Created = 0,
1633
1634 Pending = 1,
1654
1655 Complete = 2,
1667
1668 Failed = 3,
1676
1677 TimedOut = 4,
1682
1683 Refundable = 5,
1688
1689 RefundPending = 6,
1695
1696 WaitingFeeAcceptance = 7,
1708}
1709
1710impl ToSql for PaymentState {
1711 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1712 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1713 }
1714}
1715impl FromSql for PaymentState {
1716 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1717 match value {
1718 ValueRef::Integer(i) => match i as u8 {
1719 0 => Ok(PaymentState::Created),
1720 1 => Ok(PaymentState::Pending),
1721 2 => Ok(PaymentState::Complete),
1722 3 => Ok(PaymentState::Failed),
1723 4 => Ok(PaymentState::TimedOut),
1724 5 => Ok(PaymentState::Refundable),
1725 6 => Ok(PaymentState::RefundPending),
1726 7 => Ok(PaymentState::WaitingFeeAcceptance),
1727 _ => Err(FromSqlError::OutOfRange(i)),
1728 },
1729 _ => Err(FromSqlError::InvalidType),
1730 }
1731 }
1732}
1733
1734impl PaymentState {
1735 pub(crate) fn is_refundable(&self) -> bool {
1736 matches!(
1737 self,
1738 PaymentState::Refundable
1739 | PaymentState::RefundPending
1740 | PaymentState::WaitingFeeAcceptance
1741 )
1742 }
1743}
1744
1745#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1746#[strum(serialize_all = "lowercase")]
1747pub enum PaymentType {
1748 Receive = 0,
1749 Send = 1,
1750}
1751impl From<Direction> for PaymentType {
1752 fn from(value: Direction) -> Self {
1753 match value {
1754 Direction::Incoming => Self::Receive,
1755 Direction::Outgoing => Self::Send,
1756 }
1757 }
1758}
1759impl ToSql for PaymentType {
1760 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1761 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1762 }
1763}
1764impl FromSql for PaymentType {
1765 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1766 match value {
1767 ValueRef::Integer(i) => match i as u8 {
1768 0 => Ok(PaymentType::Receive),
1769 1 => Ok(PaymentType::Send),
1770 _ => Err(FromSqlError::OutOfRange(i)),
1771 },
1772 _ => Err(FromSqlError::InvalidType),
1773 }
1774 }
1775}
1776
1777#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1778pub enum PaymentStatus {
1779 Pending = 0,
1780 Complete = 1,
1781}
1782impl ToSql for PaymentStatus {
1783 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1784 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1785 }
1786}
1787impl FromSql for PaymentStatus {
1788 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1789 match value {
1790 ValueRef::Integer(i) => match i as u8 {
1791 0 => Ok(PaymentStatus::Pending),
1792 1 => Ok(PaymentStatus::Complete),
1793 _ => Err(FromSqlError::OutOfRange(i)),
1794 },
1795 _ => Err(FromSqlError::InvalidType),
1796 }
1797 }
1798}
1799
1800#[derive(Debug, Clone, Serialize)]
1801pub struct PaymentTxData {
1802 pub tx_id: String,
1804
1805 pub timestamp: Option<u32>,
1807
1808 pub fees_sat: u64,
1810
1811 pub is_confirmed: bool,
1813
1814 pub unblinding_data: Option<String>,
1817}
1818
1819#[derive(Debug, Clone, Serialize)]
1820pub enum PaymentSwapType {
1821 Receive,
1822 Send,
1823 Chain,
1824}
1825
1826#[derive(Debug, Clone, Serialize)]
1827pub struct PaymentSwapData {
1828 pub swap_id: String,
1829
1830 pub swap_type: PaymentSwapType,
1831
1832 pub created_at: u32,
1834
1835 pub expiration_blockheight: u32,
1838
1839 pub claim_expiration_blockheight: Option<u32>,
1841
1842 pub preimage: Option<String>,
1843 pub invoice: Option<String>,
1844 pub bolt12_offer: Option<String>,
1845 pub payment_hash: Option<String>,
1846 pub destination_pubkey: Option<String>,
1847 pub description: String,
1848 pub payer_note: Option<String>,
1849
1850 pub payer_amount_sat: u64,
1852
1853 pub receiver_amount_sat: u64,
1855
1856 pub swapper_fees_sat: u64,
1858
1859 pub refund_tx_id: Option<String>,
1860 pub refund_tx_amount_sat: Option<u64>,
1861
1862 pub bitcoin_address: Option<String>,
1865
1866 pub status: PaymentState,
1868}
1869
1870#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1872pub struct LnUrlInfo {
1873 pub ln_address: Option<String>,
1874 pub lnurl_pay_comment: Option<String>,
1875 pub lnurl_pay_domain: Option<String>,
1876 pub lnurl_pay_metadata: Option<String>,
1877 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1878 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1879 pub lnurl_withdraw_endpoint: Option<String>,
1880}
1881
1882#[derive(Debug, Clone, Serialize)]
1886pub struct AssetMetadata {
1887 pub asset_id: String,
1889 pub name: String,
1891 pub ticker: String,
1893 pub precision: u8,
1896 pub fiat_id: Option<String>,
1898}
1899
1900impl AssetMetadata {
1901 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1902 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1903 }
1904
1905 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1906 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1907 }
1908}
1909
1910#[derive(Clone, Debug, PartialEq, Serialize)]
1913pub struct AssetInfo {
1914 pub name: String,
1916 pub ticker: String,
1918 pub amount: f64,
1921 pub fees: Option<f64>,
1924}
1925
1926#[derive(Debug, Clone, PartialEq, Serialize)]
1928#[allow(clippy::large_enum_variant)]
1929pub enum PaymentDetails {
1930 Lightning {
1932 swap_id: String,
1933
1934 description: String,
1936
1937 liquid_expiration_blockheight: u32,
1939
1940 preimage: Option<String>,
1942
1943 invoice: Option<String>,
1947
1948 bolt12_offer: Option<String>,
1949
1950 payment_hash: Option<String>,
1952
1953 destination_pubkey: Option<String>,
1955
1956 lnurl_info: Option<LnUrlInfo>,
1958
1959 bip353_address: Option<String>,
1961
1962 payer_note: Option<String>,
1964
1965 claim_tx_id: Option<String>,
1967
1968 refund_tx_id: Option<String>,
1970
1971 refund_tx_amount_sat: Option<u64>,
1973 },
1974 Liquid {
1976 destination: String,
1978
1979 description: String,
1981
1982 asset_id: String,
1984
1985 asset_info: Option<AssetInfo>,
1987
1988 lnurl_info: Option<LnUrlInfo>,
1990
1991 bip353_address: Option<String>,
1993
1994 payer_note: Option<String>,
1996 },
1997 Bitcoin {
1999 swap_id: String,
2000
2001 bitcoin_address: String,
2003
2004 description: String,
2006
2007 auto_accepted_fees: bool,
2011
2012 liquid_expiration_blockheight: u32,
2014
2015 bitcoin_expiration_blockheight: u32,
2017
2018 lockup_tx_id: Option<String>,
2020
2021 claim_tx_id: Option<String>,
2023
2024 refund_tx_id: Option<String>,
2026
2027 refund_tx_amount_sat: Option<u64>,
2029 },
2030}
2031
2032impl PaymentDetails {
2033 pub(crate) fn get_swap_id(&self) -> Option<String> {
2034 match self {
2035 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2036 Some(swap_id.clone())
2037 }
2038 Self::Liquid { .. } => None,
2039 }
2040 }
2041
2042 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2043 match self {
2044 Self::Lightning {
2045 refund_tx_amount_sat,
2046 ..
2047 }
2048 | Self::Bitcoin {
2049 refund_tx_amount_sat,
2050 ..
2051 } => *refund_tx_amount_sat,
2052 Self::Liquid { .. } => None,
2053 }
2054 }
2055
2056 pub(crate) fn get_description(&self) -> Option<String> {
2057 match self {
2058 Self::Lightning { description, .. }
2059 | Self::Bitcoin { description, .. }
2060 | Self::Liquid { description, .. } => Some(description.clone()),
2061 }
2062 }
2063
2064 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2065 match self {
2066 Self::Liquid { asset_id, .. } => {
2067 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2068 }
2069 _ => true,
2070 }
2071 }
2072}
2073
2074#[derive(Debug, Clone, PartialEq, Serialize)]
2078pub struct Payment {
2079 pub destination: Option<String>,
2082
2083 pub tx_id: Option<String>,
2084
2085 pub unblinding_data: Option<String>,
2088
2089 pub timestamp: u32,
2095
2096 pub amount_sat: u64,
2100
2101 pub fees_sat: u64,
2115
2116 pub swapper_fees_sat: Option<u64>,
2119
2120 pub payment_type: PaymentType,
2122
2123 pub status: PaymentState,
2129
2130 pub details: PaymentDetails,
2133}
2134impl Payment {
2135 pub(crate) fn from_pending_swap(
2136 swap: PaymentSwapData,
2137 payment_type: PaymentType,
2138 payment_details: PaymentDetails,
2139 ) -> Payment {
2140 let amount_sat = match payment_type {
2141 PaymentType::Receive => swap.receiver_amount_sat,
2142 PaymentType::Send => swap.payer_amount_sat,
2143 };
2144
2145 Payment {
2146 destination: swap.invoice.clone(),
2147 tx_id: None,
2148 unblinding_data: None,
2149 timestamp: swap.created_at,
2150 amount_sat,
2151 fees_sat: swap
2152 .payer_amount_sat
2153 .saturating_sub(swap.receiver_amount_sat),
2154 swapper_fees_sat: Some(swap.swapper_fees_sat),
2155 payment_type,
2156 status: swap.status,
2157 details: payment_details,
2158 }
2159 }
2160
2161 pub(crate) fn from_tx_data(
2162 tx: PaymentTxData,
2163 balance: PaymentTxBalance,
2164 swap: Option<PaymentSwapData>,
2165 details: PaymentDetails,
2166 ) -> Payment {
2167 let (amount_sat, fees_sat) = match swap.as_ref() {
2168 Some(s) => match balance.payment_type {
2169 PaymentType::Receive => (
2173 balance.amount,
2174 s.payer_amount_sat.saturating_sub(balance.amount),
2175 ),
2176 PaymentType::Send => (
2177 s.receiver_amount_sat,
2178 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2179 ),
2180 },
2181 None => {
2182 let (amount_sat, fees_sat) = match balance.payment_type {
2183 PaymentType::Receive => (balance.amount, 0),
2184 PaymentType::Send => (balance.amount, tx.fees_sat),
2185 };
2186 match details {
2189 PaymentDetails::Liquid {
2190 asset_info: Some(ref asset_info),
2191 ..
2192 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2193 _ => (amount_sat, fees_sat),
2194 }
2195 }
2196 };
2197 Payment {
2198 tx_id: Some(tx.tx_id),
2199 unblinding_data: tx.unblinding_data,
2200 destination: match &swap {
2204 Some(PaymentSwapData {
2205 swap_type: PaymentSwapType::Receive,
2206 invoice,
2207 ..
2208 }) => invoice.clone(),
2209 Some(PaymentSwapData {
2210 swap_type: PaymentSwapType::Send,
2211 invoice,
2212 bolt12_offer,
2213 ..
2214 }) => bolt12_offer.clone().or(invoice.clone()),
2215 Some(PaymentSwapData {
2216 swap_type: PaymentSwapType::Chain,
2217 bitcoin_address,
2218 ..
2219 }) => bitcoin_address.clone(),
2220 _ => match &details {
2221 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2222 _ => None,
2223 },
2224 },
2225 timestamp: tx
2226 .timestamp
2227 .or(swap.as_ref().map(|s| s.created_at))
2228 .unwrap_or(utils::now()),
2229 amount_sat,
2230 fees_sat,
2231 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2232 payment_type: balance.payment_type,
2233 status: match &swap {
2234 Some(swap) => swap.status,
2235 None => match tx.is_confirmed {
2236 true => PaymentState::Complete,
2237 false => PaymentState::Pending,
2238 },
2239 },
2240 details,
2241 }
2242 }
2243
2244 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2245 match self.details.clone() {
2246 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2247 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2248 PaymentDetails::Liquid { .. } => None,
2249 }
2250 .flatten()
2251 }
2252}
2253
2254#[derive(Deserialize, Serialize, Clone, Debug)]
2256#[serde(rename_all = "camelCase")]
2257pub struct RecommendedFees {
2258 pub fastest_fee: u64,
2259 pub half_hour_fee: u64,
2260 pub hour_fee: u64,
2261 pub economy_fee: u64,
2262 pub minimum_fee: u64,
2263}
2264
2265#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2267pub enum BuyBitcoinProvider {
2268 #[strum(serialize = "moonpay")]
2269 Moonpay,
2270}
2271
2272#[derive(Debug, Serialize)]
2274pub struct PrepareBuyBitcoinRequest {
2275 pub provider: BuyBitcoinProvider,
2276 pub amount_sat: u64,
2277}
2278
2279#[derive(Clone, Debug, Serialize)]
2281pub struct PrepareBuyBitcoinResponse {
2282 pub provider: BuyBitcoinProvider,
2283 pub amount_sat: u64,
2284 pub fees_sat: u64,
2285}
2286
2287#[derive(Clone, Debug, Serialize)]
2289pub struct BuyBitcoinRequest {
2290 pub prepare_response: PrepareBuyBitcoinResponse,
2291 pub redirect_url: Option<String>,
2295}
2296
2297#[derive(Clone, Debug)]
2299pub struct LogEntry {
2300 pub line: String,
2301 pub level: String,
2302}
2303
2304#[derive(Clone, Debug, Serialize, Deserialize)]
2305struct InternalLeaf {
2306 pub output: String,
2307 pub version: u8,
2308}
2309impl From<InternalLeaf> for Leaf {
2310 fn from(value: InternalLeaf) -> Self {
2311 Leaf {
2312 output: value.output,
2313 version: value.version,
2314 }
2315 }
2316}
2317impl From<Leaf> for InternalLeaf {
2318 fn from(value: Leaf) -> Self {
2319 InternalLeaf {
2320 output: value.output,
2321 version: value.version,
2322 }
2323 }
2324}
2325
2326#[derive(Clone, Debug, Serialize, Deserialize)]
2327pub(super) struct InternalSwapTree {
2328 claim_leaf: InternalLeaf,
2329 refund_leaf: InternalLeaf,
2330}
2331impl From<InternalSwapTree> for SwapTree {
2332 fn from(value: InternalSwapTree) -> Self {
2333 SwapTree {
2334 claim_leaf: value.claim_leaf.into(),
2335 refund_leaf: value.refund_leaf.into(),
2336 }
2337 }
2338}
2339impl From<SwapTree> for InternalSwapTree {
2340 fn from(value: SwapTree) -> Self {
2341 InternalSwapTree {
2342 claim_leaf: value.claim_leaf.into(),
2343 refund_leaf: value.refund_leaf.into(),
2344 }
2345 }
2346}
2347
2348#[derive(Debug, Serialize)]
2350pub struct PrepareLnUrlPayRequest {
2351 pub data: LnUrlPayRequestData,
2353 pub amount: PayAmount,
2355 pub bip353_address: Option<String>,
2358 pub comment: Option<String>,
2361 pub validate_success_action_url: Option<bool>,
2364}
2365
2366#[derive(Debug, Serialize)]
2368pub struct PrepareLnUrlPayResponse {
2369 pub destination: SendDestination,
2371 pub fees_sat: u64,
2373 pub data: LnUrlPayRequestData,
2375 pub amount: PayAmount,
2377 pub comment: Option<String>,
2380 pub success_action: Option<SuccessAction>,
2383}
2384
2385#[derive(Debug, Serialize)]
2387pub struct LnUrlPayRequest {
2388 pub prepare_response: PrepareLnUrlPayResponse,
2390}
2391
2392#[derive(Serialize)]
2404#[allow(clippy::large_enum_variant)]
2405pub enum LnUrlPayResult {
2406 EndpointSuccess { data: LnUrlPaySuccessData },
2407 EndpointError { data: LnUrlErrorData },
2408 PayError { data: LnUrlPayErrorData },
2409}
2410
2411#[derive(Serialize)]
2412pub struct LnUrlPaySuccessData {
2413 pub payment: Payment,
2414 pub success_action: Option<SuccessActionProcessed>,
2415}
2416
2417#[derive(Debug, Clone)]
2418pub enum Transaction {
2419 Liquid(boltz_client::elements::Transaction),
2420 Bitcoin(boltz_client::bitcoin::Transaction),
2421}
2422
2423impl Transaction {
2424 pub(crate) fn txid(&self) -> String {
2425 match self {
2426 Transaction::Liquid(tx) => tx.txid().to_hex(),
2427 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2428 }
2429 }
2430}
2431
2432#[derive(Debug, Clone)]
2433pub enum Utxo {
2434 Liquid(
2435 Box<(
2436 boltz_client::elements::OutPoint,
2437 boltz_client::elements::TxOut,
2438 )>,
2439 ),
2440 Bitcoin(
2441 (
2442 boltz_client::bitcoin::OutPoint,
2443 boltz_client::bitcoin::TxOut,
2444 ),
2445 ),
2446}
2447
2448impl Utxo {
2449 pub(crate) fn as_bitcoin(
2450 &self,
2451 ) -> Option<&(
2452 boltz_client::bitcoin::OutPoint,
2453 boltz_client::bitcoin::TxOut,
2454 )> {
2455 match self {
2456 Utxo::Liquid(_) => None,
2457 Utxo::Bitcoin(utxo) => Some(utxo),
2458 }
2459 }
2460
2461 pub(crate) fn as_liquid(
2462 &self,
2463 ) -> Option<
2464 Box<(
2465 boltz_client::elements::OutPoint,
2466 boltz_client::elements::TxOut,
2467 )>,
2468 > {
2469 match self {
2470 Utxo::Bitcoin(_) => None,
2471 Utxo::Liquid(utxo) => Some(utxo.clone()),
2472 }
2473 }
2474}
2475
2476#[derive(Debug, Clone)]
2478pub struct FetchPaymentProposedFeesRequest {
2479 pub swap_id: String,
2480}
2481
2482#[derive(Debug, Clone, Serialize)]
2484pub struct FetchPaymentProposedFeesResponse {
2485 pub swap_id: String,
2486 pub fees_sat: u64,
2487 pub payer_amount_sat: u64,
2489 pub receiver_amount_sat: u64,
2491}
2492
2493#[derive(Debug, Clone)]
2495pub struct AcceptPaymentProposedFeesRequest {
2496 pub response: FetchPaymentProposedFeesResponse,
2497}
2498
2499#[derive(Clone, Debug)]
2500pub struct History<T> {
2501 pub txid: T,
2502 pub height: i32,
2507}
2508pub(crate) type LBtcHistory = History<elements::Txid>;
2509pub(crate) type BtcHistory = History<bitcoin::Txid>;
2510
2511impl<T> History<T> {
2512 pub(crate) fn confirmed(&self) -> bool {
2513 self.height > 0
2514 }
2515}
2516#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2517impl From<electrum_client::GetHistoryRes> for BtcHistory {
2518 fn from(value: electrum_client::GetHistoryRes) -> Self {
2519 Self {
2520 txid: value.tx_hash,
2521 height: value.height,
2522 }
2523 }
2524}
2525impl From<lwk_wollet::History> for LBtcHistory {
2526 fn from(value: lwk_wollet::History) -> Self {
2527 Self::from(&value)
2528 }
2529}
2530impl From<&lwk_wollet::History> for LBtcHistory {
2531 fn from(value: &lwk_wollet::History) -> Self {
2532 Self {
2533 txid: value.txid,
2534 height: value.height,
2535 }
2536 }
2537}
2538pub(crate) type BtcScript = bitcoin::ScriptBuf;
2539pub(crate) type LBtcScript = elements::Script;
2540
2541#[derive(Clone, Debug)]
2542pub struct BtcScriptBalance {
2543 pub confirmed: u64,
2545 pub unconfirmed: i64,
2549}
2550#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2551impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2552 fn from(val: electrum_client::GetBalanceRes) -> Self {
2553 Self {
2554 confirmed: val.confirmed,
2555 unconfirmed: val.unconfirmed,
2556 }
2557 }
2558}
2559
2560pub(crate) struct GetSyncContextRequest {
2561 pub partial_sync: Option<bool>,
2562 pub last_liquid_tip: u32,
2563 pub last_bitcoin_tip: u32,
2564}
2565
2566pub(crate) struct SyncContext {
2567 pub maybe_liquid_tip: Option<u32>,
2568 pub maybe_bitcoin_tip: Option<u32>,
2569 pub recoverable_swaps: Vec<Swap>,
2570 pub is_new_liquid_block: bool,
2571 pub is_new_bitcoin_block: bool,
2572}
2573
2574pub(crate) struct TaskHandle {
2575 pub name: String,
2576 pub handle: tokio::task::JoinHandle<()>,
2577}
2578
2579#[macro_export]
2580macro_rules! get_updated_fields {
2581 ($($var:ident),* $(,)?) => {{
2582 let mut options = Vec::new();
2583 $(
2584 if $var.is_some() {
2585 options.push(stringify!($var).to_string());
2586 }
2587 )*
2588 match options.len() > 0 {
2589 true => Some(options),
2590 false => None,
2591 }
2592 }};
2593}