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 #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
602 Lightning,
603 Bolt11Invoice,
604 Bolt12Offer,
605 BitcoinAddress,
606 LiquidAddress,
607}
608
609#[derive(Debug, Serialize, Clone)]
610pub enum ReceiveAmount {
611 Bitcoin { payer_amount_sat: u64 },
613
614 Asset {
616 asset_id: String,
617 payer_amount: Option<f64>,
618 },
619}
620
621#[derive(Debug, Serialize)]
623pub struct PrepareReceiveRequest {
624 pub payment_method: PaymentMethod,
625 pub amount: Option<ReceiveAmount>,
627}
628
629#[derive(Debug, Serialize, Clone)]
631pub struct PrepareReceiveResponse {
632 pub payment_method: PaymentMethod,
633 pub fees_sat: u64,
642 pub amount: Option<ReceiveAmount>,
644 pub min_payer_amount_sat: Option<u64>,
648 pub max_payer_amount_sat: Option<u64>,
652 pub swapper_feerate: Option<f64>,
656}
657
658#[derive(Debug, Serialize)]
660pub struct ReceivePaymentRequest {
661 pub prepare_response: PrepareReceiveResponse,
662 pub description: Option<String>,
664 pub use_description_hash: Option<bool>,
666 pub payer_note: Option<String>,
668}
669
670#[derive(Debug, Serialize)]
672pub struct ReceivePaymentResponse {
673 pub destination: String,
676 pub liquid_expiration_blockheight: Option<u32>,
678 pub bitcoin_expiration_blockheight: Option<u32>,
680}
681
682#[derive(Debug, Serialize)]
684pub struct CreateBolt12InvoiceRequest {
685 pub offer: String,
687 pub invoice_request: String,
689}
690
691#[derive(Debug, Serialize, Clone)]
693pub struct CreateBolt12InvoiceResponse {
694 pub invoice: String,
696}
697
698#[derive(Debug, Serialize)]
700pub struct Limits {
701 pub min_sat: u64,
702 pub max_sat: u64,
703 pub max_zero_conf_sat: u64,
704}
705
706#[derive(Debug, Serialize)]
708pub struct LightningPaymentLimitsResponse {
709 pub send: Limits,
711 pub receive: Limits,
713}
714
715#[derive(Debug, Serialize)]
717pub struct OnchainPaymentLimitsResponse {
718 pub send: Limits,
720 pub receive: Limits,
722}
723
724#[derive(Debug, Serialize, Clone)]
726pub struct PrepareSendRequest {
727 pub destination: String,
730 pub amount: Option<PayAmount>,
733 pub disable_mrh: Option<bool>,
735 pub payment_timeout_sec: Option<u64>,
738}
739
740#[derive(Clone, Debug, Serialize)]
742pub enum SendDestination {
743 LiquidAddress {
744 address_data: liquid::LiquidAddressData,
745 bip353_address: Option<String>,
747 },
748 Bolt11 {
749 invoice: LNInvoice,
750 bip353_address: Option<String>,
752 },
753 Bolt12 {
754 offer: LNOffer,
755 receiver_amount_sat: u64,
756 bip353_address: Option<String>,
758 },
759}
760
761#[derive(Debug, Serialize, Clone)]
763pub struct PrepareSendResponse {
764 pub destination: SendDestination,
765 pub amount: Option<PayAmount>,
767 pub fees_sat: Option<u64>,
770 pub estimated_asset_fees: Option<f64>,
774 pub exchange_amount_sat: Option<u64>,
777 pub disable_mrh: Option<bool>,
779 pub payment_timeout_sec: Option<u64>,
781}
782
783#[derive(Debug, Serialize)]
785pub struct SendPaymentRequest {
786 pub prepare_response: PrepareSendResponse,
787 pub use_asset_fees: Option<bool>,
789 pub payer_note: Option<String>,
791}
792
793#[derive(Debug, Serialize)]
795pub struct SendPaymentResponse {
796 pub payment: Payment,
797}
798
799pub(crate) struct SendPaymentViaSwapRequest {
800 pub(crate) invoice: String,
801 pub(crate) bolt12_offer: Option<String>,
802 pub(crate) payment_hash: String,
803 pub(crate) description: Option<String>,
804 pub(crate) receiver_amount_sat: u64,
805 pub(crate) fees_sat: u64,
806}
807
808pub(crate) struct PayLiquidRequest {
809 pub address_data: LiquidAddressData,
810 pub to_asset: String,
811 pub receiver_amount_sat: u64,
812 pub asset_pay_fees: bool,
813 pub fees_sat: Option<u64>,
814}
815
816pub(crate) struct PaySideSwapRequest {
817 pub address_data: LiquidAddressData,
818 pub to_asset: String,
819 pub receiver_amount_sat: u64,
820 pub fees_sat: u64,
821 pub amount: Option<PayAmount>,
822}
823
824#[derive(Debug, Serialize, Clone)]
826pub enum PayAmount {
827 Bitcoin { receiver_amount_sat: u64 },
829
830 Asset {
832 to_asset: String,
834 receiver_amount: f64,
835 estimate_asset_fees: Option<bool>,
836 from_asset: Option<String>,
839 },
840
841 Drain,
843}
844
845impl PayAmount {
846 pub(crate) fn is_sideswap_payment(&self) -> bool {
847 match self {
848 PayAmount::Asset {
849 to_asset,
850 from_asset,
851 ..
852 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
853 _ => false,
854 }
855 }
856}
857
858#[derive(Debug, Serialize, Clone)]
860pub struct PreparePayOnchainRequest {
861 pub amount: PayAmount,
863 pub fee_rate_sat_per_vbyte: Option<u32>,
865}
866
867#[derive(Debug, Serialize, Clone)]
869pub struct PreparePayOnchainResponse {
870 pub receiver_amount_sat: u64,
871 pub claim_fees_sat: u64,
872 pub total_fees_sat: u64,
873}
874
875#[derive(Debug, Serialize)]
877pub struct PayOnchainRequest {
878 pub address: String,
879 pub prepare_response: PreparePayOnchainResponse,
880}
881
882#[derive(Debug, Serialize)]
884pub struct PrepareRefundRequest {
885 pub swap_address: String,
887 pub refund_address: String,
889 pub fee_rate_sat_per_vbyte: u32,
891}
892
893#[derive(Debug, Serialize)]
895pub struct PrepareRefundResponse {
896 pub tx_vsize: u32,
897 pub tx_fee_sat: u64,
898 pub last_refund_tx_id: Option<String>,
900}
901
902#[derive(Debug, Serialize)]
904pub struct RefundRequest {
905 pub swap_address: String,
907 pub refund_address: String,
909 pub fee_rate_sat_per_vbyte: u32,
911}
912
913#[derive(Debug, Serialize)]
915pub struct RefundResponse {
916 pub refund_tx_id: String,
917}
918
919#[derive(Clone, Debug, Default, Serialize, Deserialize)]
921pub struct AssetBalance {
922 pub asset_id: String,
923 pub balance_sat: u64,
924 pub name: Option<String>,
925 pub ticker: Option<String>,
926 pub balance: Option<f64>,
927}
928
929#[derive(Debug, Serialize, Deserialize, Default)]
930pub struct BlockchainInfo {
931 pub liquid_tip: u32,
932 pub bitcoin_tip: u32,
933}
934
935#[derive(Copy, Clone)]
936pub(crate) struct ChainTips {
937 pub liquid_tip: u32,
938 pub bitcoin_tip: Option<u32>,
939}
940
941#[derive(Debug, Serialize, Deserialize)]
942pub struct WalletInfo {
943 pub balance_sat: u64,
945 pub pending_send_sat: u64,
947 pub pending_receive_sat: u64,
949 pub fingerprint: String,
951 pub pubkey: String,
953 #[serde(default)]
955 pub asset_balances: Vec<AssetBalance>,
956}
957
958impl WalletInfo {
959 pub(crate) fn validate_sufficient_funds(
960 &self,
961 network: LiquidNetwork,
962 amount_sat: u64,
963 fees_sat: Option<u64>,
964 asset_id: &str,
965 ) -> Result<(), PaymentError> {
966 let fees_sat = fees_sat.unwrap_or(0);
967 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
968 ensure_sdk!(
969 amount_sat + fees_sat <= self.balance_sat,
970 PaymentError::InsufficientFunds
971 );
972 } else {
973 match self
974 .asset_balances
975 .iter()
976 .find(|ab| ab.asset_id.eq(asset_id))
977 {
978 Some(asset_balance) => ensure_sdk!(
979 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
980 PaymentError::InsufficientFunds
981 ),
982 None => return Err(PaymentError::InsufficientFunds),
983 }
984 }
985 Ok(())
986 }
987}
988
989#[derive(Debug, Serialize, Deserialize)]
991pub struct GetInfoResponse {
992 pub wallet_info: WalletInfo,
994 #[serde(default)]
996 pub blockchain_info: BlockchainInfo,
997}
998
999#[derive(Clone, Debug, PartialEq)]
1001pub struct SignMessageRequest {
1002 pub message: String,
1003}
1004
1005#[derive(Clone, Debug, PartialEq)]
1007pub struct SignMessageResponse {
1008 pub signature: String,
1009}
1010
1011#[derive(Clone, Debug, PartialEq)]
1013pub struct CheckMessageRequest {
1014 pub message: String,
1016 pub pubkey: String,
1018 pub signature: String,
1020}
1021
1022#[derive(Clone, Debug, PartialEq)]
1024pub struct CheckMessageResponse {
1025 pub is_valid: bool,
1028}
1029
1030#[derive(Debug, Serialize)]
1032pub struct BackupRequest {
1033 pub backup_path: Option<String>,
1040}
1041
1042#[derive(Debug, Serialize)]
1044pub struct RestoreRequest {
1045 pub backup_path: Option<String>,
1046}
1047
1048#[derive(Default)]
1050pub struct ListPaymentsRequest {
1051 pub filters: Option<Vec<PaymentType>>,
1052 pub states: Option<Vec<PaymentState>>,
1053 pub from_timestamp: Option<i64>,
1055 pub to_timestamp: Option<i64>,
1057 pub offset: Option<u32>,
1058 pub limit: Option<u32>,
1059 pub details: Option<ListPaymentDetails>,
1060 pub sort_ascending: Option<bool>,
1061}
1062
1063#[derive(Debug, Serialize)]
1065pub enum ListPaymentDetails {
1066 Liquid {
1068 asset_id: Option<String>,
1070 destination: Option<String>,
1072 },
1073
1074 Bitcoin {
1076 address: Option<String>,
1078 },
1079}
1080
1081#[derive(Debug, Serialize)]
1083pub enum GetPaymentRequest {
1084 PaymentHash { payment_hash: String },
1086 SwapId { swap_id: String },
1088}
1089
1090#[sdk_macros::async_trait]
1092pub(crate) trait BlockListener: Send + Sync {
1093 async fn on_bitcoin_block(&self, height: u32);
1094 async fn on_liquid_block(&self, height: u32);
1095}
1096
1097#[derive(Clone, Debug)]
1099pub enum Swap {
1100 Chain(ChainSwap),
1101 Send(SendSwap),
1102 Receive(ReceiveSwap),
1103}
1104impl Swap {
1105 pub(crate) fn id(&self) -> String {
1106 match &self {
1107 Swap::Chain(ChainSwap { id, .. })
1108 | Swap::Send(SendSwap { id, .. })
1109 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1110 }
1111 }
1112
1113 pub(crate) fn version(&self) -> u64 {
1114 match self {
1115 Swap::Chain(ChainSwap { metadata, .. })
1116 | Swap::Send(SendSwap { metadata, .. })
1117 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1118 }
1119 }
1120
1121 pub(crate) fn set_version(&mut self, version: u64) {
1122 match self {
1123 Swap::Chain(chain_swap) => {
1124 chain_swap.metadata.version = version;
1125 }
1126 Swap::Send(send_swap) => {
1127 send_swap.metadata.version = version;
1128 }
1129 Swap::Receive(receive_swap) => {
1130 receive_swap.metadata.version = version;
1131 }
1132 }
1133 }
1134
1135 pub(crate) fn last_updated_at(&self) -> u32 {
1136 match self {
1137 Swap::Chain(ChainSwap { metadata, .. })
1138 | Swap::Send(SendSwap { metadata, .. })
1139 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1140 }
1141 }
1142}
1143impl From<ChainSwap> for Swap {
1144 fn from(swap: ChainSwap) -> Self {
1145 Self::Chain(swap)
1146 }
1147}
1148impl From<SendSwap> for Swap {
1149 fn from(swap: SendSwap) -> Self {
1150 Self::Send(swap)
1151 }
1152}
1153impl From<ReceiveSwap> for Swap {
1154 fn from(swap: ReceiveSwap) -> Self {
1155 Self::Receive(swap)
1156 }
1157}
1158
1159#[derive(Clone, Debug)]
1160pub(crate) enum SwapScriptV2 {
1161 Bitcoin(BtcSwapScript),
1162 Liquid(LBtcSwapScript),
1163}
1164impl SwapScriptV2 {
1165 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1166 match self {
1167 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1168 _ => Err(anyhow!("Invalid chain")),
1169 }
1170 }
1171
1172 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1173 match self {
1174 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1175 _ => Err(anyhow!("Invalid chain")),
1176 }
1177 }
1178}
1179
1180#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1181pub enum Direction {
1182 Incoming = 0,
1183 Outgoing = 1,
1184}
1185impl ToSql for Direction {
1186 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1187 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1188 }
1189}
1190impl FromSql for Direction {
1191 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1192 match value {
1193 ValueRef::Integer(i) => match i as u8 {
1194 0 => Ok(Direction::Incoming),
1195 1 => Ok(Direction::Outgoing),
1196 _ => Err(FromSqlError::OutOfRange(i)),
1197 },
1198 _ => Err(FromSqlError::InvalidType),
1199 }
1200 }
1201}
1202
1203#[derive(Clone, Debug, Default)]
1204pub(crate) struct SwapMetadata {
1205 pub(crate) version: u64,
1207 pub(crate) last_updated_at: u32,
1208 pub(crate) is_local: bool,
1209}
1210
1211#[derive(Clone, Debug, Derivative)]
1215#[derivative(PartialEq)]
1216pub struct ChainSwap {
1217 pub(crate) id: String,
1218 pub(crate) direction: Direction,
1219 pub(crate) claim_address: Option<String>,
1222 pub(crate) lockup_address: String,
1223 pub(crate) refund_address: Option<String>,
1225 pub(crate) timeout_block_height: u32,
1227 pub(crate) claim_timeout_block_height: u32,
1229 pub(crate) preimage: String,
1230 pub(crate) description: Option<String>,
1231 pub(crate) payer_amount_sat: u64,
1233 pub(crate) actual_payer_amount_sat: Option<u64>,
1236 pub(crate) receiver_amount_sat: u64,
1238 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1240 pub(crate) claim_fees_sat: u64,
1241 pub(crate) pair_fees_json: String,
1243 pub(crate) accept_zero_conf: bool,
1244 pub(crate) create_response_json: String,
1246 pub(crate) server_lockup_tx_id: Option<String>,
1248 pub(crate) user_lockup_tx_id: Option<String>,
1250 pub(crate) claim_tx_id: Option<String>,
1252 pub(crate) refund_tx_id: Option<String>,
1254 pub(crate) created_at: u32,
1255 pub(crate) state: PaymentState,
1256 pub(crate) claim_private_key: String,
1257 pub(crate) refund_private_key: String,
1258 pub(crate) auto_accepted_fees: bool,
1259 #[derivative(PartialEq = "ignore")]
1261 pub(crate) metadata: SwapMetadata,
1262}
1263impl ChainSwap {
1264 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1265 utils::decode_keypair(&self.claim_private_key)
1266 }
1267
1268 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1269 utils::decode_keypair(&self.refund_private_key)
1270 }
1271
1272 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1273 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1274 serde_json::from_str(&self.create_response_json).map_err(|e| {
1275 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1276 })?;
1277
1278 Ok(CreateChainResponse {
1279 id: self.id.clone(),
1280 claim_details: internal_create_response.claim_details,
1281 lockup_details: internal_create_response.lockup_details,
1282 })
1283 }
1284
1285 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1286 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1287 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1288
1289 Ok(pair)
1290 }
1291
1292 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1293 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1294 let our_pubkey = self.get_claim_keypair()?.public_key();
1295 let swap_script = match self.direction {
1296 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1297 Side::Claim,
1298 chain_swap_details,
1299 our_pubkey.into(),
1300 )?),
1301 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1302 Side::Claim,
1303 chain_swap_details,
1304 our_pubkey.into(),
1305 )?),
1306 };
1307 Ok(swap_script)
1308 }
1309
1310 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1311 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1312 let our_pubkey = self.get_refund_keypair()?.public_key();
1313 let swap_script = match self.direction {
1314 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1315 Side::Lockup,
1316 chain_swap_details,
1317 our_pubkey.into(),
1318 )?),
1319 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1320 Side::Lockup,
1321 chain_swap_details,
1322 our_pubkey.into(),
1323 )?),
1324 };
1325 Ok(swap_script)
1326 }
1327
1328 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1330 &self,
1331 network: LiquidNetwork,
1332 ) -> SdkResult<ScriptBuf> {
1333 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1334 let script_pubkey = swap_script
1335 .to_address(network.as_bitcoin_chain())
1336 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1337 .script_pubkey();
1338 Ok(script_pubkey)
1339 }
1340
1341 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1342 RefundableSwap {
1343 swap_address: self.lockup_address.clone(),
1344 timestamp: self.created_at,
1345 amount_sat,
1346 last_refund_tx_id: self.refund_tx_id.clone(),
1347 }
1348 }
1349
1350 pub(crate) fn from_boltz_struct_to_json(
1351 create_response: &CreateChainResponse,
1352 expected_swap_id: &str,
1353 ) -> Result<String, PaymentError> {
1354 let internal_create_response =
1355 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1356 create_response,
1357 expected_swap_id,
1358 )?;
1359
1360 let create_response_json =
1361 serde_json::to_string(&internal_create_response).map_err(|e| {
1362 PaymentError::Generic {
1363 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1364 }
1365 })?;
1366
1367 Ok(create_response_json)
1368 }
1369
1370 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1371 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1372 }
1373}
1374
1375#[derive(Clone, Debug, Default)]
1376pub(crate) struct ChainSwapUpdate {
1377 pub(crate) swap_id: String,
1378 pub(crate) to_state: PaymentState,
1379 pub(crate) server_lockup_tx_id: Option<String>,
1380 pub(crate) user_lockup_tx_id: Option<String>,
1381 pub(crate) claim_address: Option<String>,
1382 pub(crate) claim_tx_id: Option<String>,
1383 pub(crate) refund_tx_id: Option<String>,
1384}
1385
1386#[derive(Clone, Debug, Derivative)]
1388#[derivative(PartialEq)]
1389pub struct SendSwap {
1390 pub(crate) id: String,
1391 pub(crate) invoice: String,
1393 pub(crate) bolt12_offer: Option<String>,
1395 pub(crate) payment_hash: Option<String>,
1396 pub(crate) destination_pubkey: Option<String>,
1397 pub(crate) description: Option<String>,
1398 pub(crate) preimage: Option<String>,
1399 pub(crate) payer_amount_sat: u64,
1400 pub(crate) receiver_amount_sat: u64,
1401 pub(crate) pair_fees_json: String,
1403 pub(crate) create_response_json: String,
1405 pub(crate) lockup_tx_id: Option<String>,
1407 pub(crate) refund_address: Option<String>,
1409 pub(crate) refund_tx_id: Option<String>,
1411 pub(crate) created_at: u32,
1412 pub(crate) timeout_block_height: u64,
1413 pub(crate) state: PaymentState,
1414 pub(crate) refund_private_key: String,
1415 #[derivative(PartialEq = "ignore")]
1417 pub(crate) metadata: SwapMetadata,
1418}
1419impl SendSwap {
1420 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1421 utils::decode_keypair(&self.refund_private_key)
1422 }
1423
1424 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1425 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1426 serde_json::from_str(&self.create_response_json).map_err(|e| {
1427 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1428 })?;
1429
1430 let res = CreateSubmarineResponse {
1431 id: self.id.clone(),
1432 accept_zero_conf: internal_create_response.accept_zero_conf,
1433 address: internal_create_response.address.clone(),
1434 bip21: internal_create_response.bip21.clone(),
1435 claim_public_key: crate::utils::json_to_pubkey(
1436 &internal_create_response.claim_public_key,
1437 )?,
1438 expected_amount: internal_create_response.expected_amount,
1439 referral_id: internal_create_response.referral_id,
1440 swap_tree: internal_create_response.swap_tree.clone().into(),
1441 timeout_block_height: internal_create_response.timeout_block_height,
1442 blinding_key: internal_create_response.blinding_key.clone(),
1443 };
1444 Ok(res)
1445 }
1446
1447 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1448 LBtcSwapScript::submarine_from_swap_resp(
1449 &self.get_boltz_create_response()?,
1450 self.get_refund_keypair()?.public_key().into(),
1451 )
1452 .map_err(|e| {
1453 SdkError::generic(format!(
1454 "Failed to create swap script for Send Swap {}: {e:?}",
1455 self.id
1456 ))
1457 })
1458 }
1459
1460 pub(crate) fn from_boltz_struct_to_json(
1461 create_response: &CreateSubmarineResponse,
1462 expected_swap_id: &str,
1463 ) -> Result<String, PaymentError> {
1464 let internal_create_response =
1465 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1466 create_response,
1467 expected_swap_id,
1468 )?;
1469
1470 let create_response_json =
1471 serde_json::to_string(&internal_create_response).map_err(|e| {
1472 PaymentError::Generic {
1473 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1474 }
1475 })?;
1476
1477 Ok(create_response_json)
1478 }
1479}
1480
1481#[derive(Clone, Debug, Derivative)]
1483#[derivative(PartialEq)]
1484pub struct ReceiveSwap {
1485 pub(crate) id: String,
1486 pub(crate) preimage: String,
1487 pub(crate) create_response_json: String,
1489 pub(crate) claim_private_key: String,
1490 pub(crate) invoice: String,
1491 pub(crate) bolt12_offer: Option<String>,
1493 pub(crate) payment_hash: Option<String>,
1494 pub(crate) destination_pubkey: Option<String>,
1495 pub(crate) description: Option<String>,
1496 pub(crate) payer_note: Option<String>,
1497 pub(crate) payer_amount_sat: u64,
1499 pub(crate) receiver_amount_sat: u64,
1500 pub(crate) pair_fees_json: String,
1502 pub(crate) claim_fees_sat: u64,
1503 pub(crate) claim_address: Option<String>,
1505 pub(crate) claim_tx_id: Option<String>,
1507 pub(crate) lockup_tx_id: Option<String>,
1509 pub(crate) mrh_address: String,
1511 pub(crate) mrh_tx_id: Option<String>,
1513 pub(crate) created_at: u32,
1516 pub(crate) timeout_block_height: u32,
1517 pub(crate) state: PaymentState,
1518 #[derivative(PartialEq = "ignore")]
1520 pub(crate) metadata: SwapMetadata,
1521}
1522impl ReceiveSwap {
1523 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1524 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1525 }
1526
1527 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1528 Ok(self
1529 .get_swap_script()?
1530 .funding_addrs
1531 .ok_or(anyhow!("No funding address found"))?
1532 .script_pubkey())
1533 }
1534
1535 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1536 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1537 serde_json::from_str(&self.create_response_json).map_err(|e| {
1538 PaymentError::Generic {
1539 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1540 }
1541 })?;
1542
1543 let res = CreateReverseResponse {
1544 id: self.id.clone(),
1545 invoice: Some(self.invoice.clone()),
1546 swap_tree: internal_create_response.swap_tree.clone().into(),
1547 lockup_address: internal_create_response.lockup_address.clone(),
1548 refund_public_key: crate::utils::json_to_pubkey(
1549 &internal_create_response.refund_public_key,
1550 )?,
1551 timeout_block_height: internal_create_response.timeout_block_height,
1552 onchain_amount: internal_create_response.onchain_amount,
1553 blinding_key: internal_create_response.blinding_key.clone(),
1554 };
1555 Ok(res)
1556 }
1557
1558 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1559 let keypair = self.get_claim_keypair()?;
1560 let create_response =
1561 self.get_boltz_create_response()
1562 .map_err(|e| PaymentError::Generic {
1563 err: format!(
1564 "Failed to create swap script for Receive Swap {}: {e:?}",
1565 self.id
1566 ),
1567 })?;
1568 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1569 .map_err(|e| PaymentError::Generic {
1570 err: format!(
1571 "Failed to create swap script for Receive Swap {}: {e:?}",
1572 self.id
1573 ),
1574 })
1575 }
1576
1577 pub(crate) fn from_boltz_struct_to_json(
1578 create_response: &CreateReverseResponse,
1579 expected_swap_id: &str,
1580 expected_invoice: Option<&str>,
1581 ) -> Result<String, PaymentError> {
1582 let internal_create_response =
1583 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1584 create_response,
1585 expected_swap_id,
1586 expected_invoice,
1587 )?;
1588
1589 let create_response_json =
1590 serde_json::to_string(&internal_create_response).map_err(|e| {
1591 PaymentError::Generic {
1592 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1593 }
1594 })?;
1595
1596 Ok(create_response_json)
1597 }
1598}
1599
1600#[derive(Clone, Debug, PartialEq, Serialize)]
1602pub struct RefundableSwap {
1603 pub swap_address: String,
1604 pub timestamp: u32,
1605 pub amount_sat: u64,
1607 pub last_refund_tx_id: Option<String>,
1609}
1610
1611#[derive(Clone, Debug, Derivative)]
1613#[derivative(PartialEq)]
1614pub(crate) struct Bolt12Offer {
1615 pub(crate) id: String,
1617 pub(crate) description: String,
1619 pub(crate) private_key: String,
1621 pub(crate) webhook_url: Option<String>,
1623 pub(crate) created_at: u32,
1625}
1626impl Bolt12Offer {
1627 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1628 utils::decode_keypair(&self.private_key)
1629 }
1630}
1631impl TryFrom<Bolt12Offer> for Offer {
1632 type Error = SdkError;
1633
1634 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1635 Offer::from_str(&val.id)
1636 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1637 }
1638}
1639
1640#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1642#[strum(serialize_all = "lowercase")]
1643pub enum PaymentState {
1644 #[default]
1645 Created = 0,
1646
1647 Pending = 1,
1667
1668 Complete = 2,
1680
1681 Failed = 3,
1689
1690 TimedOut = 4,
1695
1696 Refundable = 5,
1701
1702 RefundPending = 6,
1708
1709 WaitingFeeAcceptance = 7,
1721}
1722
1723impl ToSql for PaymentState {
1724 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1725 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1726 }
1727}
1728impl FromSql for PaymentState {
1729 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1730 match value {
1731 ValueRef::Integer(i) => match i as u8 {
1732 0 => Ok(PaymentState::Created),
1733 1 => Ok(PaymentState::Pending),
1734 2 => Ok(PaymentState::Complete),
1735 3 => Ok(PaymentState::Failed),
1736 4 => Ok(PaymentState::TimedOut),
1737 5 => Ok(PaymentState::Refundable),
1738 6 => Ok(PaymentState::RefundPending),
1739 7 => Ok(PaymentState::WaitingFeeAcceptance),
1740 _ => Err(FromSqlError::OutOfRange(i)),
1741 },
1742 _ => Err(FromSqlError::InvalidType),
1743 }
1744 }
1745}
1746
1747impl PaymentState {
1748 pub(crate) fn is_refundable(&self) -> bool {
1749 matches!(
1750 self,
1751 PaymentState::Refundable
1752 | PaymentState::RefundPending
1753 | PaymentState::WaitingFeeAcceptance
1754 )
1755 }
1756}
1757
1758#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1759#[strum(serialize_all = "lowercase")]
1760pub enum PaymentType {
1761 Receive = 0,
1762 Send = 1,
1763}
1764impl From<Direction> for PaymentType {
1765 fn from(value: Direction) -> Self {
1766 match value {
1767 Direction::Incoming => Self::Receive,
1768 Direction::Outgoing => Self::Send,
1769 }
1770 }
1771}
1772impl ToSql for PaymentType {
1773 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1774 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1775 }
1776}
1777impl FromSql for PaymentType {
1778 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1779 match value {
1780 ValueRef::Integer(i) => match i as u8 {
1781 0 => Ok(PaymentType::Receive),
1782 1 => Ok(PaymentType::Send),
1783 _ => Err(FromSqlError::OutOfRange(i)),
1784 },
1785 _ => Err(FromSqlError::InvalidType),
1786 }
1787 }
1788}
1789
1790#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1791pub enum PaymentStatus {
1792 Pending = 0,
1793 Complete = 1,
1794}
1795impl ToSql for PaymentStatus {
1796 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1797 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1798 }
1799}
1800impl FromSql for PaymentStatus {
1801 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1802 match value {
1803 ValueRef::Integer(i) => match i as u8 {
1804 0 => Ok(PaymentStatus::Pending),
1805 1 => Ok(PaymentStatus::Complete),
1806 _ => Err(FromSqlError::OutOfRange(i)),
1807 },
1808 _ => Err(FromSqlError::InvalidType),
1809 }
1810 }
1811}
1812
1813#[derive(Debug, Clone, Serialize)]
1814pub struct PaymentTxData {
1815 pub tx_id: String,
1817
1818 pub timestamp: Option<u32>,
1820
1821 pub fees_sat: u64,
1823
1824 pub is_confirmed: bool,
1826
1827 pub unblinding_data: Option<String>,
1830}
1831
1832#[derive(Debug, Clone, Serialize)]
1833pub enum PaymentSwapType {
1834 Receive,
1835 Send,
1836 Chain,
1837}
1838
1839#[derive(Debug, Clone, Serialize)]
1840pub struct PaymentSwapData {
1841 pub swap_id: String,
1842
1843 pub swap_type: PaymentSwapType,
1844
1845 pub created_at: u32,
1847
1848 pub expiration_blockheight: u32,
1851
1852 pub claim_expiration_blockheight: Option<u32>,
1854
1855 pub preimage: Option<String>,
1856 pub invoice: Option<String>,
1857 pub bolt12_offer: Option<String>,
1858 pub payment_hash: Option<String>,
1859 pub destination_pubkey: Option<String>,
1860 pub description: String,
1861 pub payer_note: Option<String>,
1862
1863 pub payer_amount_sat: u64,
1865
1866 pub receiver_amount_sat: u64,
1868
1869 pub swapper_fees_sat: u64,
1871
1872 pub refund_tx_id: Option<String>,
1873 pub refund_tx_amount_sat: Option<u64>,
1874
1875 pub bitcoin_address: Option<String>,
1878
1879 pub status: PaymentState,
1881}
1882
1883#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1885pub struct LnUrlInfo {
1886 pub ln_address: Option<String>,
1887 pub lnurl_pay_comment: Option<String>,
1888 pub lnurl_pay_domain: Option<String>,
1889 pub lnurl_pay_metadata: Option<String>,
1890 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1891 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1892 pub lnurl_withdraw_endpoint: Option<String>,
1893}
1894
1895#[derive(Debug, Clone, Serialize)]
1899pub struct AssetMetadata {
1900 pub asset_id: String,
1902 pub name: String,
1904 pub ticker: String,
1906 pub precision: u8,
1909 pub fiat_id: Option<String>,
1911}
1912
1913impl AssetMetadata {
1914 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1915 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1916 }
1917
1918 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1919 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1920 }
1921}
1922
1923#[derive(Clone, Debug, PartialEq, Serialize)]
1926pub struct AssetInfo {
1927 pub name: String,
1929 pub ticker: String,
1931 pub amount: f64,
1934 pub fees: Option<f64>,
1937}
1938
1939#[derive(Debug, Clone, PartialEq, Serialize)]
1941#[allow(clippy::large_enum_variant)]
1942pub enum PaymentDetails {
1943 Lightning {
1945 swap_id: String,
1946
1947 description: String,
1949
1950 liquid_expiration_blockheight: u32,
1952
1953 preimage: Option<String>,
1955
1956 invoice: Option<String>,
1960
1961 bolt12_offer: Option<String>,
1962
1963 payment_hash: Option<String>,
1965
1966 destination_pubkey: Option<String>,
1968
1969 lnurl_info: Option<LnUrlInfo>,
1971
1972 bip353_address: Option<String>,
1974
1975 payer_note: Option<String>,
1977
1978 claim_tx_id: Option<String>,
1980
1981 refund_tx_id: Option<String>,
1983
1984 refund_tx_amount_sat: Option<u64>,
1986 },
1987 Liquid {
1989 destination: String,
1991
1992 description: String,
1994
1995 asset_id: String,
1997
1998 asset_info: Option<AssetInfo>,
2000
2001 lnurl_info: Option<LnUrlInfo>,
2003
2004 bip353_address: Option<String>,
2006
2007 payer_note: Option<String>,
2009 },
2010 Bitcoin {
2012 swap_id: String,
2013
2014 bitcoin_address: String,
2016
2017 description: String,
2019
2020 auto_accepted_fees: bool,
2024
2025 liquid_expiration_blockheight: u32,
2027
2028 bitcoin_expiration_blockheight: u32,
2030
2031 lockup_tx_id: Option<String>,
2033
2034 claim_tx_id: Option<String>,
2036
2037 refund_tx_id: Option<String>,
2039
2040 refund_tx_amount_sat: Option<u64>,
2042 },
2043}
2044
2045impl PaymentDetails {
2046 pub(crate) fn get_swap_id(&self) -> Option<String> {
2047 match self {
2048 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2049 Some(swap_id.clone())
2050 }
2051 Self::Liquid { .. } => None,
2052 }
2053 }
2054
2055 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2056 match self {
2057 Self::Lightning {
2058 refund_tx_amount_sat,
2059 ..
2060 }
2061 | Self::Bitcoin {
2062 refund_tx_amount_sat,
2063 ..
2064 } => *refund_tx_amount_sat,
2065 Self::Liquid { .. } => None,
2066 }
2067 }
2068
2069 pub(crate) fn get_description(&self) -> Option<String> {
2070 match self {
2071 Self::Lightning { description, .. }
2072 | Self::Bitcoin { description, .. }
2073 | Self::Liquid { description, .. } => Some(description.clone()),
2074 }
2075 }
2076
2077 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2078 match self {
2079 Self::Liquid { asset_id, .. } => {
2080 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2081 }
2082 _ => true,
2083 }
2084 }
2085}
2086
2087#[derive(Debug, Clone, PartialEq, Serialize)]
2091pub struct Payment {
2092 pub destination: Option<String>,
2095
2096 pub tx_id: Option<String>,
2097
2098 pub unblinding_data: Option<String>,
2101
2102 pub timestamp: u32,
2108
2109 pub amount_sat: u64,
2113
2114 pub fees_sat: u64,
2128
2129 pub swapper_fees_sat: Option<u64>,
2132
2133 pub payment_type: PaymentType,
2135
2136 pub status: PaymentState,
2142
2143 pub details: PaymentDetails,
2146}
2147impl Payment {
2148 pub(crate) fn from_pending_swap(
2149 swap: PaymentSwapData,
2150 payment_type: PaymentType,
2151 payment_details: PaymentDetails,
2152 ) -> Payment {
2153 let amount_sat = match payment_type {
2154 PaymentType::Receive => swap.receiver_amount_sat,
2155 PaymentType::Send => swap.payer_amount_sat,
2156 };
2157
2158 Payment {
2159 destination: swap.invoice.clone(),
2160 tx_id: None,
2161 unblinding_data: None,
2162 timestamp: swap.created_at,
2163 amount_sat,
2164 fees_sat: swap
2165 .payer_amount_sat
2166 .saturating_sub(swap.receiver_amount_sat),
2167 swapper_fees_sat: Some(swap.swapper_fees_sat),
2168 payment_type,
2169 status: swap.status,
2170 details: payment_details,
2171 }
2172 }
2173
2174 pub(crate) fn from_tx_data(
2175 tx: PaymentTxData,
2176 balance: PaymentTxBalance,
2177 swap: Option<PaymentSwapData>,
2178 details: PaymentDetails,
2179 ) -> Payment {
2180 let (amount_sat, fees_sat) = match swap.as_ref() {
2181 Some(s) => match balance.payment_type {
2182 PaymentType::Receive => (
2186 balance.amount,
2187 s.payer_amount_sat.saturating_sub(balance.amount),
2188 ),
2189 PaymentType::Send => (
2190 s.receiver_amount_sat,
2191 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2192 ),
2193 },
2194 None => {
2195 let (amount_sat, fees_sat) = match balance.payment_type {
2196 PaymentType::Receive => (balance.amount, 0),
2197 PaymentType::Send => (balance.amount, tx.fees_sat),
2198 };
2199 match details {
2202 PaymentDetails::Liquid {
2203 asset_info: Some(ref asset_info),
2204 ..
2205 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2206 _ => (amount_sat, fees_sat),
2207 }
2208 }
2209 };
2210 Payment {
2211 tx_id: Some(tx.tx_id),
2212 unblinding_data: tx.unblinding_data,
2213 destination: match &swap {
2217 Some(PaymentSwapData {
2218 swap_type: PaymentSwapType::Receive,
2219 invoice,
2220 ..
2221 }) => invoice.clone(),
2222 Some(PaymentSwapData {
2223 swap_type: PaymentSwapType::Send,
2224 invoice,
2225 bolt12_offer,
2226 ..
2227 }) => bolt12_offer.clone().or(invoice.clone()),
2228 Some(PaymentSwapData {
2229 swap_type: PaymentSwapType::Chain,
2230 bitcoin_address,
2231 ..
2232 }) => bitcoin_address.clone(),
2233 _ => match &details {
2234 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2235 _ => None,
2236 },
2237 },
2238 timestamp: tx
2239 .timestamp
2240 .or(swap.as_ref().map(|s| s.created_at))
2241 .unwrap_or(utils::now()),
2242 amount_sat,
2243 fees_sat,
2244 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2245 payment_type: balance.payment_type,
2246 status: match &swap {
2247 Some(swap) => swap.status,
2248 None => match tx.is_confirmed {
2249 true => PaymentState::Complete,
2250 false => PaymentState::Pending,
2251 },
2252 },
2253 details,
2254 }
2255 }
2256
2257 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2258 match self.details.clone() {
2259 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2260 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2261 PaymentDetails::Liquid { .. } => None,
2262 }
2263 .flatten()
2264 }
2265}
2266
2267#[derive(Deserialize, Serialize, Clone, Debug)]
2269#[serde(rename_all = "camelCase")]
2270pub struct RecommendedFees {
2271 pub fastest_fee: u64,
2272 pub half_hour_fee: u64,
2273 pub hour_fee: u64,
2274 pub economy_fee: u64,
2275 pub minimum_fee: u64,
2276}
2277
2278#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2280pub enum BuyBitcoinProvider {
2281 #[strum(serialize = "moonpay")]
2282 Moonpay,
2283}
2284
2285#[derive(Debug, Serialize)]
2287pub struct PrepareBuyBitcoinRequest {
2288 pub provider: BuyBitcoinProvider,
2289 pub amount_sat: u64,
2290}
2291
2292#[derive(Clone, Debug, Serialize)]
2294pub struct PrepareBuyBitcoinResponse {
2295 pub provider: BuyBitcoinProvider,
2296 pub amount_sat: u64,
2297 pub fees_sat: u64,
2298}
2299
2300#[derive(Clone, Debug, Serialize)]
2302pub struct BuyBitcoinRequest {
2303 pub prepare_response: PrepareBuyBitcoinResponse,
2304 pub redirect_url: Option<String>,
2308}
2309
2310#[derive(Clone, Debug)]
2312pub struct LogEntry {
2313 pub line: String,
2314 pub level: String,
2315}
2316
2317#[derive(Clone, Debug, Serialize, Deserialize)]
2318struct InternalLeaf {
2319 pub output: String,
2320 pub version: u8,
2321}
2322impl From<InternalLeaf> for Leaf {
2323 fn from(value: InternalLeaf) -> Self {
2324 Leaf {
2325 output: value.output,
2326 version: value.version,
2327 }
2328 }
2329}
2330impl From<Leaf> for InternalLeaf {
2331 fn from(value: Leaf) -> Self {
2332 InternalLeaf {
2333 output: value.output,
2334 version: value.version,
2335 }
2336 }
2337}
2338
2339#[derive(Clone, Debug, Serialize, Deserialize)]
2340pub(super) struct InternalSwapTree {
2341 claim_leaf: InternalLeaf,
2342 refund_leaf: InternalLeaf,
2343}
2344impl From<InternalSwapTree> for SwapTree {
2345 fn from(value: InternalSwapTree) -> Self {
2346 SwapTree {
2347 claim_leaf: value.claim_leaf.into(),
2348 refund_leaf: value.refund_leaf.into(),
2349 }
2350 }
2351}
2352impl From<SwapTree> for InternalSwapTree {
2353 fn from(value: SwapTree) -> Self {
2354 InternalSwapTree {
2355 claim_leaf: value.claim_leaf.into(),
2356 refund_leaf: value.refund_leaf.into(),
2357 }
2358 }
2359}
2360
2361#[derive(Debug, Serialize)]
2363pub struct PrepareLnUrlPayRequest {
2364 pub data: LnUrlPayRequestData,
2366 pub amount: PayAmount,
2368 pub bip353_address: Option<String>,
2371 pub comment: Option<String>,
2374 pub validate_success_action_url: Option<bool>,
2377}
2378
2379#[derive(Debug, Serialize)]
2381pub struct PrepareLnUrlPayResponse {
2382 pub destination: SendDestination,
2384 pub fees_sat: u64,
2386 pub data: LnUrlPayRequestData,
2388 pub amount: PayAmount,
2390 pub comment: Option<String>,
2393 pub success_action: Option<SuccessAction>,
2396}
2397
2398#[derive(Debug, Serialize)]
2400pub struct LnUrlPayRequest {
2401 pub prepare_response: PrepareLnUrlPayResponse,
2403}
2404
2405#[derive(Serialize)]
2417#[allow(clippy::large_enum_variant)]
2418pub enum LnUrlPayResult {
2419 EndpointSuccess { data: LnUrlPaySuccessData },
2420 EndpointError { data: LnUrlErrorData },
2421 PayError { data: LnUrlPayErrorData },
2422}
2423
2424#[derive(Serialize)]
2425pub struct LnUrlPaySuccessData {
2426 pub payment: Payment,
2427 pub success_action: Option<SuccessActionProcessed>,
2428}
2429
2430#[derive(Debug, Clone)]
2431pub enum Transaction {
2432 Liquid(boltz_client::elements::Transaction),
2433 Bitcoin(boltz_client::bitcoin::Transaction),
2434}
2435
2436impl Transaction {
2437 pub(crate) fn txid(&self) -> String {
2438 match self {
2439 Transaction::Liquid(tx) => tx.txid().to_hex(),
2440 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2441 }
2442 }
2443}
2444
2445#[derive(Debug, Clone)]
2446pub enum Utxo {
2447 Liquid(
2448 Box<(
2449 boltz_client::elements::OutPoint,
2450 boltz_client::elements::TxOut,
2451 )>,
2452 ),
2453 Bitcoin(
2454 (
2455 boltz_client::bitcoin::OutPoint,
2456 boltz_client::bitcoin::TxOut,
2457 ),
2458 ),
2459}
2460
2461impl Utxo {
2462 pub(crate) fn as_bitcoin(
2463 &self,
2464 ) -> Option<&(
2465 boltz_client::bitcoin::OutPoint,
2466 boltz_client::bitcoin::TxOut,
2467 )> {
2468 match self {
2469 Utxo::Liquid(_) => None,
2470 Utxo::Bitcoin(utxo) => Some(utxo),
2471 }
2472 }
2473
2474 pub(crate) fn as_liquid(
2475 &self,
2476 ) -> Option<
2477 Box<(
2478 boltz_client::elements::OutPoint,
2479 boltz_client::elements::TxOut,
2480 )>,
2481 > {
2482 match self {
2483 Utxo::Bitcoin(_) => None,
2484 Utxo::Liquid(utxo) => Some(utxo.clone()),
2485 }
2486 }
2487}
2488
2489#[derive(Debug, Clone)]
2491pub struct FetchPaymentProposedFeesRequest {
2492 pub swap_id: String,
2493}
2494
2495#[derive(Debug, Clone, Serialize)]
2497pub struct FetchPaymentProposedFeesResponse {
2498 pub swap_id: String,
2499 pub fees_sat: u64,
2500 pub payer_amount_sat: u64,
2502 pub receiver_amount_sat: u64,
2504}
2505
2506#[derive(Debug, Clone)]
2508pub struct AcceptPaymentProposedFeesRequest {
2509 pub response: FetchPaymentProposedFeesResponse,
2510}
2511
2512#[derive(Clone, Debug)]
2513pub struct History<T> {
2514 pub txid: T,
2515 pub height: i32,
2520}
2521pub(crate) type LBtcHistory = History<elements::Txid>;
2522pub(crate) type BtcHistory = History<bitcoin::Txid>;
2523
2524impl<T> History<T> {
2525 pub(crate) fn confirmed(&self) -> bool {
2526 self.height > 0
2527 }
2528}
2529#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2530impl From<electrum_client::GetHistoryRes> for BtcHistory {
2531 fn from(value: electrum_client::GetHistoryRes) -> Self {
2532 Self {
2533 txid: value.tx_hash,
2534 height: value.height,
2535 }
2536 }
2537}
2538impl From<lwk_wollet::History> for LBtcHistory {
2539 fn from(value: lwk_wollet::History) -> Self {
2540 Self::from(&value)
2541 }
2542}
2543impl From<&lwk_wollet::History> for LBtcHistory {
2544 fn from(value: &lwk_wollet::History) -> Self {
2545 Self {
2546 txid: value.txid,
2547 height: value.height,
2548 }
2549 }
2550}
2551pub(crate) type BtcScript = bitcoin::ScriptBuf;
2552pub(crate) type LBtcScript = elements::Script;
2553
2554#[derive(Clone, Debug)]
2555pub struct BtcScriptBalance {
2556 pub confirmed: u64,
2558 pub unconfirmed: i64,
2562}
2563#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2564impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2565 fn from(val: electrum_client::GetBalanceRes) -> Self {
2566 Self {
2567 confirmed: val.confirmed,
2568 unconfirmed: val.unconfirmed,
2569 }
2570 }
2571}
2572
2573pub(crate) struct GetSyncContextRequest {
2574 pub partial_sync: Option<bool>,
2575 pub last_liquid_tip: u32,
2576 pub last_bitcoin_tip: u32,
2577}
2578
2579pub(crate) struct SyncContext {
2580 pub maybe_liquid_tip: Option<u32>,
2581 pub maybe_bitcoin_tip: Option<u32>,
2582 pub recoverable_swaps: Vec<Swap>,
2583 pub is_new_liquid_block: bool,
2584 pub is_new_bitcoin_block: bool,
2585}
2586
2587pub(crate) struct TaskHandle {
2588 pub name: String,
2589 pub handle: tokio::task::JoinHandle<()>,
2590}
2591
2592#[macro_export]
2593macro_rules! get_updated_fields {
2594 ($($var:ident),* $(,)?) => {{
2595 let mut options = Vec::new();
2596 $(
2597 if $var.is_some() {
2598 options.push(stringify!($var).to_string());
2599 }
2600 )*
2601 match options.len() > 0 {
2602 true => Some(options),
2603 false => None,
2604 }
2605 }};
2606}