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