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