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 rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
14use rusqlite::ToSql;
15use sdk_common::prelude::*;
16use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
17use serde::{Deserialize, Serialize};
18use std::path::PathBuf;
19use std::str::FromStr;
20use std::{cmp::PartialEq, sync::Arc};
21use strum_macros::{Display, EnumString};
22use tokio_with_wasm::alias as tokio;
23
24use crate::{
25 bitcoin,
26 chain::{
27 bitcoin::{esplora::EsploraBitcoinChainService, BitcoinChainService},
28 liquid::{esplora::EsploraLiquidChainService, LiquidChainService},
29 },
30 elements,
31 error::{PaymentError, SdkError, SdkResult},
32 persist::model::PaymentTxBalance,
33 prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
34 receive_swap::DEFAULT_ZERO_CONF_MAX_SAT,
35 side_swap::api::{SIDESWAP_MAINNET_URL, SIDESWAP_TESTNET_URL},
36 utils,
37};
38
39pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
41pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
42pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
43pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api";
44pub const BREEZ_SWAP_PROXY_URL: &str = "https://swap.breez.technology/v2";
45pub const DEFAULT_ONCHAIN_FEE_RATE_LEEWAY_SAT: u64 = 500;
46const DEFAULT_ONCHAIN_SYNC_PERIOD_SEC: u32 = 10;
47const DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC: u32 = 7;
48
49const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
50
51#[derive(Clone, Debug, Serialize)]
52pub enum BlockchainExplorer {
53 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
54 Electrum { url: String },
55 Esplora {
56 url: String,
57 use_waterfalls: bool,
59 },
60}
61
62#[derive(Clone, Debug, Serialize)]
64pub struct Config {
65 pub liquid_explorer: BlockchainExplorer,
66 pub bitcoin_explorer: BlockchainExplorer,
67 pub working_dir: String,
71 pub network: LiquidNetwork,
72 pub payment_timeout_sec: u64,
74 pub sync_service_url: Option<String>,
77 pub zero_conf_max_amount_sat: Option<u64>,
80 pub breez_api_key: Option<String>,
82 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
86 pub use_default_external_input_parsers: bool,
90 pub onchain_fee_rate_leeway_sat: Option<u64>,
97 pub asset_metadata: Option<Vec<AssetMetadata>>,
102 pub sideswap_api_key: Option<String>,
104 pub use_magic_routing_hints: bool,
106 pub onchain_sync_period_sec: u32,
108 pub onchain_sync_request_timeout_sec: u32,
110}
111
112impl Config {
113 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
114 pub fn mainnet(breez_api_key: Option<String>) -> Self {
115 Config {
116 liquid_explorer: BlockchainExplorer::Electrum {
117 url: "elements-mainnet.breez.technology:50002".to_string(),
118 },
119 bitcoin_explorer: BlockchainExplorer::Electrum {
120 url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
121 },
122 working_dir: ".".to_string(),
123 network: LiquidNetwork::Mainnet,
124 payment_timeout_sec: 15,
125 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
126 zero_conf_max_amount_sat: None,
127 breez_api_key,
128 external_input_parsers: None,
129 use_default_external_input_parsers: true,
130 onchain_fee_rate_leeway_sat: None,
131 asset_metadata: None,
132 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
133 use_magic_routing_hints: true,
134 onchain_sync_period_sec: DEFAULT_ONCHAIN_SYNC_PERIOD_SEC,
135 onchain_sync_request_timeout_sec: DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC,
136 }
137 }
138
139 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
140 Config {
141 liquid_explorer: BlockchainExplorer::Esplora {
142 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
143 use_waterfalls: true,
144 },
145 bitcoin_explorer: BlockchainExplorer::Esplora {
146 url: "https://blockstream.info/api/".to_string(),
147 use_waterfalls: false,
148 },
149 working_dir: ".".to_string(),
150 network: LiquidNetwork::Mainnet,
151 payment_timeout_sec: 15,
152 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
153 zero_conf_max_amount_sat: None,
154 breez_api_key,
155 external_input_parsers: None,
156 use_default_external_input_parsers: true,
157 onchain_fee_rate_leeway_sat: None,
158 asset_metadata: None,
159 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
160 use_magic_routing_hints: true,
161 onchain_sync_period_sec: DEFAULT_ONCHAIN_SYNC_PERIOD_SEC,
162 onchain_sync_request_timeout_sec: DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC,
163 }
164 }
165
166 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
167 pub fn regtest() -> Self {
168 Config {
169 liquid_explorer: BlockchainExplorer::Electrum {
170 url: "localhost:19002".to_string(),
171 },
172 bitcoin_explorer: BlockchainExplorer::Electrum {
173 url: "localhost:19001".to_string(),
174 },
175 working_dir: ".".to_string(),
176 network: LiquidNetwork::Regtest,
177 payment_timeout_sec: 15,
178 sync_service_url: Some("http://localhost:8088".to_string()),
179 zero_conf_max_amount_sat: None,
180 breez_api_key: None,
181 external_input_parsers: None,
182 use_default_external_input_parsers: true,
183 onchain_fee_rate_leeway_sat: None,
184 asset_metadata: None,
185 sideswap_api_key: None,
186 use_magic_routing_hints: true,
187 onchain_sync_period_sec: DEFAULT_ONCHAIN_SYNC_PERIOD_SEC,
188 onchain_sync_request_timeout_sec: DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC,
189 }
190 }
191
192 pub fn regtest_esplora() -> Self {
193 Config {
194 liquid_explorer: BlockchainExplorer::Esplora {
195 url: "http://localhost:3120/api".to_string(),
196 use_waterfalls: true,
197 },
198 bitcoin_explorer: BlockchainExplorer::Esplora {
199 url: "http://localhost:4002/api".to_string(),
200 use_waterfalls: false,
201 },
202 working_dir: ".".to_string(),
203 network: LiquidNetwork::Regtest,
204 payment_timeout_sec: 15,
205 sync_service_url: Some("http://localhost:8089".to_string()),
206 zero_conf_max_amount_sat: None,
207 breez_api_key: None,
208 external_input_parsers: None,
209 use_default_external_input_parsers: true,
210 onchain_fee_rate_leeway_sat: None,
211 asset_metadata: None,
212 sideswap_api_key: None,
213 use_magic_routing_hints: true,
214 onchain_sync_period_sec: DEFAULT_ONCHAIN_SYNC_PERIOD_SEC,
215 onchain_sync_request_timeout_sec: DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC,
216 }
217 }
218
219 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
220 Ok(PathBuf::from(base_dir)
221 .join(match self.network {
222 LiquidNetwork::Mainnet => "mainnet",
223 LiquidNetwork::Testnet => "testnet",
224 LiquidNetwork::Regtest => "regtest",
225 })
226 .join(fingerprint_hex)
227 .to_str()
228 .ok_or(anyhow::anyhow!(
229 "Could not get retrieve current wallet directory"
230 ))?
231 .to_string())
232 }
233
234 pub fn zero_conf_max_amount_sat(&self) -> u64 {
235 self.zero_conf_max_amount_sat
236 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
237 }
238
239 pub(crate) fn lbtc_asset_id(&self) -> String {
240 utils::lbtc_asset_id(self.network).to_string()
241 }
242
243 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
244 let mut external_input_parsers = Vec::new();
245 if self.use_default_external_input_parsers {
246 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
247 .iter()
248 .map(|(id, regex, url)| ExternalInputParser {
249 provider_id: id.to_string(),
250 input_regex: regex.to_string(),
251 parser_url: url.to_string(),
252 })
253 .collect::<Vec<_>>();
254 external_input_parsers.extend(default_parsers);
255 }
256 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
257
258 external_input_parsers
259 }
260
261 pub(crate) fn default_boltz_url(&self) -> &str {
262 match self.network {
263 LiquidNetwork::Mainnet => {
264 if self.breez_api_key.is_some() {
265 BREEZ_SWAP_PROXY_URL
266 } else {
267 BOLTZ_MAINNET_URL_V2
268 }
269 }
270 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
271 LiquidNetwork::Regtest => "http://localhost:8387/v2",
273 }
274 }
275
276 pub fn sync_enabled(&self) -> bool {
277 self.sync_service_url.is_some()
278 }
279
280 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
281 match self.bitcoin_explorer {
282 BlockchainExplorer::Esplora { .. } => {
283 Arc::new(EsploraBitcoinChainService::new(self.clone()))
284 }
285 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
286 BlockchainExplorer::Electrum { .. } => Arc::new(
287 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
288 ),
289 }
290 }
291
292 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
293 match &self.liquid_explorer {
294 BlockchainExplorer::Esplora { url, .. } => {
295 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
296 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")
297 }
298 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
299 }
300 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
301 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
302 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
303 )),
304 }
305 }
306
307 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
308 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
309 match self.network {
310 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
311 LiquidNetwork::Regtest => (false, false),
312 }
313 }
314
315 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
316 pub(crate) fn electrum_client(
317 &self,
318 url: &str,
319 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
320 let (tls, validate_domain) = self.electrum_tls_options();
321 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
322 lwk_wollet::ElectrumClient::with_options(
323 &electrum_url,
324 lwk_wollet::ElectrumOptions {
325 timeout: Some(self.onchain_sync_request_timeout_sec as u8),
326 },
327 )
328 }
329
330 pub(crate) fn sideswap_url(&self) -> &'static str {
331 match self.network {
332 LiquidNetwork::Mainnet => SIDESWAP_MAINNET_URL,
333 LiquidNetwork::Testnet => SIDESWAP_TESTNET_URL,
334 LiquidNetwork::Regtest => unimplemented!(),
335 }
336 }
337}
338
339#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
342pub enum LiquidNetwork {
343 Mainnet,
345 Testnet,
350 Regtest,
352}
353impl LiquidNetwork {
354 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
355 match self {
356 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
357 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
358 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
359 }
360 }
361}
362
363impl From<LiquidNetwork> for lwk_wollet::Network {
364 fn from(value: LiquidNetwork) -> Self {
365 match value {
366 LiquidNetwork::Mainnet => lwk_wollet::Network::Liquid,
367 LiquidNetwork::Testnet => lwk_wollet::Network::TestnetLiquid,
368 LiquidNetwork::Regtest => lwk_wollet::Network::CustomElements(
369 lwk_common::ElementsParamsBuilder::new()
370 .with_policy_asset(
371 AssetId::from_str(
372 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
373 )
374 .unwrap(),
375 )
376 .build()
377 .expect("valid regtest elements params"),
378 ),
379 }
380 }
381}
382
383impl From<LiquidNetwork> for Chain {
384 fn from(value: LiquidNetwork) -> Self {
385 Chain::Liquid(value.into())
386 }
387}
388
389impl From<LiquidNetwork> for LiquidChain {
390 fn from(value: LiquidNetwork) -> Self {
391 match value {
392 LiquidNetwork::Mainnet => LiquidChain::Liquid,
393 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
394 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
395 }
396 }
397}
398
399impl TryFrom<&str> for LiquidNetwork {
400 type Error = anyhow::Error;
401
402 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
403 match value.to_lowercase().as_str() {
404 "mainnet" => Ok(LiquidNetwork::Mainnet),
405 "testnet" => Ok(LiquidNetwork::Testnet),
406 "regtest" => Ok(LiquidNetwork::Regtest),
407 _ => Err(anyhow!("Invalid network")),
408 }
409 }
410}
411
412impl From<LiquidNetwork> for Network {
413 fn from(value: LiquidNetwork) -> Self {
414 match value {
415 LiquidNetwork::Mainnet => Self::Bitcoin,
416 LiquidNetwork::Testnet => Self::Testnet,
417 LiquidNetwork::Regtest => Self::Regtest,
418 }
419 }
420}
421
422impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
423 fn from(value: LiquidNetwork) -> Self {
424 match value {
425 LiquidNetwork::Mainnet => Self::Bitcoin,
426 LiquidNetwork::Testnet => Self::Testnet,
427 LiquidNetwork::Regtest => Self::Regtest,
428 }
429 }
430}
431
432impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
433 fn from(value: LiquidNetwork) -> Self {
434 match value {
435 LiquidNetwork::Mainnet => Self::Bitcoin,
436 LiquidNetwork::Testnet => Self::Testnet,
437 LiquidNetwork::Regtest => Self::Regtest,
438 }
439 }
440}
441
442#[sdk_macros::async_trait]
444pub trait EventListener: Send + Sync {
445 async fn on_event(&self, e: SdkEvent);
446}
447
448#[derive(Clone, Debug, PartialEq)]
451pub enum SdkEvent {
452 PaymentFailed {
453 details: Payment,
454 },
455 PaymentPending {
456 details: Payment,
457 },
458 PaymentRefundable {
459 details: Payment,
460 },
461 PaymentRefunded {
462 details: Payment,
463 },
464 PaymentRefundPending {
465 details: Payment,
466 },
467 PaymentSucceeded {
468 details: Payment,
469 },
470 PaymentWaitingConfirmation {
471 details: Payment,
472 },
473 PaymentWaitingFeeAcceptance {
474 details: Payment,
475 },
476 Synced,
478 SyncFailed {
480 error: String,
481 },
482 DataSynced {
484 did_pull_new_records: bool,
486 },
487}
488
489#[derive(thiserror::Error, Debug)]
490pub enum SignerError {
491 #[error("Signer error: {err}")]
492 Generic { err: String },
493}
494
495impl From<anyhow::Error> for SignerError {
496 fn from(err: anyhow::Error) -> Self {
497 SignerError::Generic {
498 err: err.to_string(),
499 }
500 }
501}
502
503impl From<bip32::Error> for SignerError {
504 fn from(err: bip32::Error) -> Self {
505 SignerError::Generic {
506 err: err.to_string(),
507 }
508 }
509}
510
511pub trait Signer: Send + Sync {
514 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
517
518 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
524
525 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
527
528 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
530
531 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
533
534 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
537
538 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
540
541 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
543}
544
545pub struct ConnectRequest {
548 pub config: Config,
550 pub mnemonic: Option<String>,
552 pub passphrase: Option<String>,
554 pub seed: Option<Vec<u8>>,
556}
557
558pub struct ConnectWithSignerRequest {
559 pub config: Config,
560}
561
562#[derive(Clone, Debug)]
565pub(crate) struct ReservedAddress {
566 pub(crate) address: String,
568 pub(crate) expiry_block_height: u32,
570}
571
572#[derive(Clone, Debug, Serialize)]
574pub enum PaymentMethod {
575 Bolt11Invoice,
576 Bolt12Offer,
577 BitcoinAddress,
578 LiquidAddress,
579}
580
581#[derive(Debug, Serialize, Clone)]
582pub enum ReceiveAmount {
583 Bitcoin { payer_amount_sat: u64 },
585
586 Asset {
588 asset_id: String,
589 payer_amount: Option<f64>,
590 },
591}
592
593#[derive(Debug, Serialize)]
595pub struct PrepareReceiveRequest {
596 pub payment_method: PaymentMethod,
597 pub amount: Option<ReceiveAmount>,
599}
600
601#[derive(Debug, Serialize, Clone)]
603pub struct PrepareReceiveResponse {
604 pub payment_method: PaymentMethod,
605 pub fees_sat: u64,
614 pub amount: Option<ReceiveAmount>,
616 pub min_payer_amount_sat: Option<u64>,
620 pub max_payer_amount_sat: Option<u64>,
624 pub swapper_feerate: Option<f64>,
628}
629
630#[derive(Clone, Debug, Serialize)]
631pub enum DescriptionHash {
632 UseDescription,
633 Custom { hash: String },
634}
635
636#[derive(Debug, Serialize)]
638pub struct ReceivePaymentRequest {
639 pub prepare_response: PrepareReceiveResponse,
640 pub description: Option<String>,
642 pub description_hash: Option<DescriptionHash>,
646 pub payer_note: Option<String>,
648}
649
650#[derive(Debug, Serialize)]
652pub struct ReceivePaymentResponse {
653 pub destination: String,
656 pub liquid_expiration_blockheight: Option<u32>,
658 pub bitcoin_expiration_blockheight: Option<u32>,
660}
661
662#[derive(Debug, Serialize)]
664pub struct CreateBolt12InvoiceRequest {
665 pub offer: String,
667 pub invoice_request: String,
669}
670
671#[derive(Debug, Serialize, Clone)]
673pub struct CreateBolt12InvoiceResponse {
674 pub invoice: String,
676}
677
678#[derive(Debug, Serialize)]
680pub struct Limits {
681 pub min_sat: u64,
682 pub max_sat: u64,
683 pub max_zero_conf_sat: u64,
684}
685
686#[derive(Debug, Serialize)]
688pub struct LightningPaymentLimitsResponse {
689 pub send: Limits,
691 pub receive: Limits,
693}
694
695#[derive(Debug, Serialize)]
697pub struct OnchainPaymentLimitsResponse {
698 pub send: Limits,
700 pub receive: Limits,
702}
703
704#[derive(Debug, Serialize, Clone)]
706pub struct PrepareSendRequest {
707 pub destination: String,
710 pub amount: Option<PayAmount>,
713 pub disable_mrh: Option<bool>,
715 pub payment_timeout_sec: Option<u64>,
718}
719
720#[derive(Clone, Debug, Serialize)]
722pub enum SendDestination {
723 LiquidAddress {
724 address_data: liquid::LiquidAddressData,
725 bip353_address: Option<String>,
727 },
728 Bolt11 {
729 invoice: LNInvoice,
730 bip353_address: Option<String>,
732 },
733 Bolt12 {
734 offer: LNOffer,
735 receiver_amount_sat: u64,
736 bip353_address: Option<String>,
738 },
739}
740
741#[derive(Debug, Serialize, Clone)]
743pub struct PrepareSendResponse {
744 pub destination: SendDestination,
745 pub amount: Option<PayAmount>,
747 pub fees_sat: Option<u64>,
750 pub estimated_asset_fees: Option<f64>,
754 pub exchange_amount_sat: Option<u64>,
757 pub disable_mrh: Option<bool>,
759 pub payment_timeout_sec: Option<u64>,
761}
762
763#[derive(Debug, Serialize)]
765pub struct SendPaymentRequest {
766 pub prepare_response: PrepareSendResponse,
767 pub use_asset_fees: Option<bool>,
769 pub payer_note: Option<String>,
771}
772
773#[derive(Debug, Serialize)]
775pub struct SendPaymentResponse {
776 pub payment: Payment,
777}
778
779pub(crate) struct SendPaymentViaSwapRequest {
780 pub(crate) invoice: String,
781 pub(crate) bolt12_offer: Option<String>,
782 pub(crate) payment_hash: String,
783 pub(crate) description: Option<String>,
784 pub(crate) receiver_amount_sat: u64,
785 pub(crate) fees_sat: u64,
786}
787
788pub(crate) struct PayLiquidRequest {
789 pub address_data: LiquidAddressData,
790 pub to_asset: String,
791 pub receiver_amount_sat: u64,
792 pub asset_pay_fees: bool,
793 pub fees_sat: Option<u64>,
794}
795
796pub(crate) struct PaySideSwapRequest {
797 pub address_data: LiquidAddressData,
798 pub to_asset: String,
799 pub receiver_amount_sat: u64,
800 pub fees_sat: u64,
801 pub amount: Option<PayAmount>,
802}
803
804#[derive(Debug, Serialize, Clone)]
806pub enum PayAmount {
807 Bitcoin { receiver_amount_sat: u64 },
809
810 Asset {
812 to_asset: String,
814 receiver_amount: f64,
815 estimate_asset_fees: Option<bool>,
816 from_asset: Option<String>,
819 },
820
821 Drain,
823}
824
825impl PayAmount {
826 pub(crate) fn is_sideswap_payment(&self) -> bool {
827 match self {
828 PayAmount::Asset {
829 to_asset,
830 from_asset,
831 ..
832 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
833 _ => false,
834 }
835 }
836}
837
838#[derive(Debug, Serialize, Clone)]
840pub struct PreparePayOnchainRequest {
841 pub amount: PayAmount,
843 pub fee_rate_sat_per_vbyte: Option<u32>,
845}
846
847#[derive(Debug, Serialize, Clone)]
849pub struct PreparePayOnchainResponse {
850 pub receiver_amount_sat: u64,
851 pub claim_fees_sat: u64,
852 pub total_fees_sat: u64,
853}
854
855#[derive(Debug, Serialize)]
857pub struct PayOnchainRequest {
858 pub address: String,
859 pub prepare_response: PreparePayOnchainResponse,
860}
861
862#[derive(Debug, Serialize)]
864pub struct PrepareRefundRequest {
865 pub swap_address: String,
867 pub refund_address: String,
869 pub fee_rate_sat_per_vbyte: u32,
871}
872
873#[derive(Debug, Serialize)]
875pub struct PrepareRefundResponse {
876 pub tx_vsize: u32,
877 pub tx_fee_sat: u64,
878 pub last_refund_tx_id: Option<String>,
880}
881
882#[derive(Debug, Serialize)]
884pub struct RefundRequest {
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 RefundResponse {
896 pub refund_tx_id: String,
897}
898
899#[derive(Clone, Debug, Default, Serialize, Deserialize)]
901pub struct AssetBalance {
902 pub asset_id: String,
903 pub balance_sat: u64,
904 pub name: Option<String>,
905 pub ticker: Option<String>,
906 pub balance: Option<f64>,
907}
908
909#[derive(Debug, Serialize, Deserialize, Default)]
910pub struct BlockchainInfo {
911 pub liquid_tip: u32,
912 pub bitcoin_tip: u32,
913}
914
915#[derive(Copy, Clone)]
916pub(crate) struct ChainTips {
917 pub liquid_tip: u32,
918 pub bitcoin_tip: Option<u32>,
919}
920
921#[derive(Debug, Serialize, Deserialize)]
922pub struct WalletInfo {
923 pub balance_sat: u64,
925 pub pending_send_sat: u64,
927 pub pending_receive_sat: u64,
929 pub fingerprint: String,
931 pub pubkey: String,
933 #[serde(default)]
935 pub asset_balances: Vec<AssetBalance>,
936}
937
938impl WalletInfo {
939 pub(crate) fn validate_sufficient_funds(
940 &self,
941 network: LiquidNetwork,
942 amount_sat: u64,
943 fees_sat: Option<u64>,
944 asset_id: &str,
945 ) -> Result<(), PaymentError> {
946 let fees_sat = fees_sat.unwrap_or(0);
947 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
948 ensure_sdk!(
949 amount_sat + fees_sat <= self.balance_sat,
950 PaymentError::InsufficientFunds
951 );
952 } else {
953 match self
954 .asset_balances
955 .iter()
956 .find(|ab| ab.asset_id.eq(asset_id))
957 {
958 Some(asset_balance) => ensure_sdk!(
959 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
960 PaymentError::InsufficientFunds
961 ),
962 None => return Err(PaymentError::InsufficientFunds),
963 }
964 }
965 Ok(())
966 }
967}
968
969#[derive(Debug, Serialize, Deserialize)]
971pub struct GetInfoResponse {
972 pub wallet_info: WalletInfo,
974 #[serde(default)]
976 pub blockchain_info: BlockchainInfo,
977}
978
979#[derive(Clone, Debug, PartialEq)]
981pub struct SignMessageRequest {
982 pub message: String,
983}
984
985#[derive(Clone, Debug, PartialEq)]
987pub struct SignMessageResponse {
988 pub signature: String,
989}
990
991#[derive(Clone, Debug, PartialEq)]
993pub struct CheckMessageRequest {
994 pub message: String,
996 pub pubkey: String,
998 pub signature: String,
1000}
1001
1002#[derive(Clone, Debug, PartialEq)]
1004pub struct CheckMessageResponse {
1005 pub is_valid: bool,
1008}
1009
1010#[derive(Debug, Serialize)]
1012pub struct BackupRequest {
1013 pub backup_path: Option<String>,
1020}
1021
1022#[derive(Debug, Serialize)]
1024pub struct RestoreRequest {
1025 pub backup_path: Option<String>,
1026}
1027
1028#[derive(Default)]
1030pub struct ListPaymentsRequest {
1031 pub filters: Option<Vec<PaymentType>>,
1032 pub states: Option<Vec<PaymentState>>,
1033 pub from_timestamp: Option<i64>,
1035 pub to_timestamp: Option<i64>,
1037 pub offset: Option<u32>,
1038 pub limit: Option<u32>,
1039 pub details: Option<ListPaymentDetails>,
1040 pub sort_ascending: Option<bool>,
1041}
1042
1043#[derive(Debug, Serialize)]
1045pub enum ListPaymentDetails {
1046 Liquid {
1048 asset_id: Option<String>,
1050 destination: Option<String>,
1052 },
1053
1054 Bitcoin {
1056 address: Option<String>,
1058 },
1059}
1060
1061#[derive(Debug, Serialize)]
1063pub enum GetPaymentRequest {
1064 PaymentHash { payment_hash: String },
1066 SwapId { swap_id: String },
1068}
1069
1070#[sdk_macros::async_trait]
1072pub(crate) trait BlockListener: Send + Sync {
1073 async fn on_bitcoin_block(&self, height: u32);
1074 async fn on_liquid_block(&self, height: u32);
1075}
1076
1077#[derive(Clone, Debug)]
1079pub enum Swap {
1080 Chain(ChainSwap),
1081 Send(SendSwap),
1082 Receive(ReceiveSwap),
1083}
1084impl Swap {
1085 pub(crate) fn id(&self) -> String {
1086 match &self {
1087 Swap::Chain(ChainSwap { id, .. })
1088 | Swap::Send(SendSwap { id, .. })
1089 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1090 }
1091 }
1092
1093 pub(crate) fn version(&self) -> u64 {
1094 match self {
1095 Swap::Chain(ChainSwap { metadata, .. })
1096 | Swap::Send(SendSwap { metadata, .. })
1097 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1098 }
1099 }
1100
1101 pub(crate) fn set_version(&mut self, version: u64) {
1102 match self {
1103 Swap::Chain(chain_swap) => {
1104 chain_swap.metadata.version = version;
1105 }
1106 Swap::Send(send_swap) => {
1107 send_swap.metadata.version = version;
1108 }
1109 Swap::Receive(receive_swap) => {
1110 receive_swap.metadata.version = version;
1111 }
1112 }
1113 }
1114
1115 pub(crate) fn last_updated_at(&self) -> u32 {
1116 match self {
1117 Swap::Chain(ChainSwap { metadata, .. })
1118 | Swap::Send(SendSwap { metadata, .. })
1119 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1120 }
1121 }
1122}
1123impl From<ChainSwap> for Swap {
1124 fn from(swap: ChainSwap) -> Self {
1125 Self::Chain(swap)
1126 }
1127}
1128impl From<SendSwap> for Swap {
1129 fn from(swap: SendSwap) -> Self {
1130 Self::Send(swap)
1131 }
1132}
1133impl From<ReceiveSwap> for Swap {
1134 fn from(swap: ReceiveSwap) -> Self {
1135 Self::Receive(swap)
1136 }
1137}
1138
1139#[derive(Clone, Debug)]
1140pub(crate) enum SwapScriptV2 {
1141 Bitcoin(BtcSwapScript),
1142 Liquid(LBtcSwapScript),
1143}
1144impl SwapScriptV2 {
1145 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1146 match self {
1147 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1148 _ => Err(anyhow!("Invalid chain")),
1149 }
1150 }
1151
1152 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1153 match self {
1154 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1155 _ => Err(anyhow!("Invalid chain")),
1156 }
1157 }
1158}
1159
1160#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1161pub enum Direction {
1162 Incoming = 0,
1163 Outgoing = 1,
1164}
1165impl ToSql for Direction {
1166 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1167 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1168 }
1169}
1170impl FromSql for Direction {
1171 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1172 match value {
1173 ValueRef::Integer(i) => match i as u8 {
1174 0 => Ok(Direction::Incoming),
1175 1 => Ok(Direction::Outgoing),
1176 _ => Err(FromSqlError::OutOfRange(i)),
1177 },
1178 _ => Err(FromSqlError::InvalidType),
1179 }
1180 }
1181}
1182
1183#[derive(Clone, Debug, Default)]
1184pub(crate) struct SwapMetadata {
1185 pub(crate) version: u64,
1187 pub(crate) last_updated_at: u32,
1188 pub(crate) is_local: bool,
1189}
1190
1191#[derive(Clone, Debug, Derivative)]
1195#[derivative(PartialEq)]
1196pub struct ChainSwap {
1197 pub(crate) id: String,
1198 pub(crate) direction: Direction,
1199 pub(crate) claim_address: Option<String>,
1202 pub(crate) lockup_address: String,
1203 pub(crate) refund_address: Option<String>,
1205 pub(crate) timeout_block_height: u32,
1207 pub(crate) claim_timeout_block_height: u32,
1209 pub(crate) preimage: String,
1210 pub(crate) description: Option<String>,
1211 pub(crate) payer_amount_sat: u64,
1213 pub(crate) actual_payer_amount_sat: Option<u64>,
1216 pub(crate) receiver_amount_sat: u64,
1218 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1220 pub(crate) claim_fees_sat: u64,
1221 pub(crate) pair_fees_json: String,
1223 pub(crate) accept_zero_conf: bool,
1224 pub(crate) create_response_json: String,
1226 pub(crate) server_lockup_tx_id: Option<String>,
1228 pub(crate) user_lockup_tx_id: Option<String>,
1230 pub(crate) claim_tx_id: Option<String>,
1232 pub(crate) refund_tx_id: Option<String>,
1234 pub(crate) created_at: u32,
1235 pub(crate) state: PaymentState,
1236 pub(crate) claim_private_key: String,
1237 pub(crate) refund_private_key: String,
1238 pub(crate) auto_accepted_fees: bool,
1239 pub(crate) user_lockup_spent: bool,
1242 #[derivative(PartialEq = "ignore")]
1244 pub(crate) metadata: SwapMetadata,
1245}
1246impl ChainSwap {
1247 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1248 utils::decode_keypair(&self.claim_private_key)
1249 }
1250
1251 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1252 utils::decode_keypair(&self.refund_private_key)
1253 }
1254
1255 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1256 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1257 serde_json::from_str(&self.create_response_json).map_err(|e| {
1258 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1259 })?;
1260
1261 Ok(CreateChainResponse {
1262 id: self.id.clone(),
1263 claim_details: internal_create_response.claim_details,
1264 lockup_details: internal_create_response.lockup_details,
1265 })
1266 }
1267
1268 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1269 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1270 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1271
1272 Ok(pair)
1273 }
1274
1275 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1276 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1277 let our_pubkey = self.get_claim_keypair()?.public_key();
1278 let swap_script = match self.direction {
1279 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1280 Side::Claim,
1281 chain_swap_details,
1282 our_pubkey.into(),
1283 )?),
1284 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1285 Side::Claim,
1286 chain_swap_details,
1287 our_pubkey.into(),
1288 )?),
1289 };
1290 Ok(swap_script)
1291 }
1292
1293 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1294 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1295 let our_pubkey = self.get_refund_keypair()?.public_key();
1296 let swap_script = match self.direction {
1297 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1298 Side::Lockup,
1299 chain_swap_details,
1300 our_pubkey.into(),
1301 )?),
1302 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1303 Side::Lockup,
1304 chain_swap_details,
1305 our_pubkey.into(),
1306 )?),
1307 };
1308 Ok(swap_script)
1309 }
1310
1311 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1313 &self,
1314 network: LiquidNetwork,
1315 ) -> SdkResult<ScriptBuf> {
1316 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1317 let script_pubkey = swap_script
1318 .to_address(network.as_bitcoin_chain())
1319 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1320 .script_pubkey();
1321 Ok(script_pubkey)
1322 }
1323
1324 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1325 RefundableSwap {
1326 swap_address: self.lockup_address.clone(),
1327 timestamp: self.created_at,
1328 amount_sat,
1329 last_refund_tx_id: self.refund_tx_id.clone(),
1330 }
1331 }
1332
1333 pub(crate) fn from_boltz_struct_to_json(
1334 create_response: &CreateChainResponse,
1335 expected_swap_id: &str,
1336 ) -> Result<String, PaymentError> {
1337 let internal_create_response =
1338 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1339 create_response,
1340 expected_swap_id,
1341 )?;
1342
1343 let create_response_json =
1344 serde_json::to_string(&internal_create_response).map_err(|e| {
1345 PaymentError::Generic {
1346 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1347 }
1348 })?;
1349
1350 Ok(create_response_json)
1351 }
1352
1353 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1354 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1355 }
1356}
1357
1358#[derive(Clone, Debug, Default)]
1359pub(crate) struct ChainSwapUpdate {
1360 pub(crate) swap_id: String,
1361 pub(crate) to_state: PaymentState,
1362 pub(crate) server_lockup_tx_id: Option<String>,
1363 pub(crate) user_lockup_tx_id: Option<String>,
1364 pub(crate) claim_address: Option<String>,
1365 pub(crate) claim_tx_id: Option<String>,
1366 pub(crate) refund_tx_id: Option<String>,
1367}
1368
1369#[derive(Clone, Debug, Derivative)]
1371#[derivative(PartialEq)]
1372pub struct SendSwap {
1373 pub(crate) id: String,
1374 pub(crate) invoice: String,
1376 pub(crate) bolt12_offer: Option<String>,
1378 pub(crate) payment_hash: Option<String>,
1379 pub(crate) destination_pubkey: Option<String>,
1380 pub(crate) description: Option<String>,
1381 pub(crate) preimage: Option<String>,
1382 pub(crate) payer_amount_sat: u64,
1383 pub(crate) receiver_amount_sat: u64,
1384 pub(crate) pair_fees_json: String,
1386 pub(crate) create_response_json: String,
1388 pub(crate) lockup_tx_id: Option<String>,
1390 pub(crate) refund_address: Option<String>,
1392 pub(crate) refund_tx_id: Option<String>,
1394 pub(crate) created_at: u32,
1395 pub(crate) timeout_block_height: u64,
1396 pub(crate) state: PaymentState,
1397 pub(crate) refund_private_key: String,
1398 #[derivative(PartialEq = "ignore")]
1400 pub(crate) metadata: SwapMetadata,
1401}
1402impl SendSwap {
1403 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1404 utils::decode_keypair(&self.refund_private_key)
1405 }
1406
1407 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1408 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1409 serde_json::from_str(&self.create_response_json).map_err(|e| {
1410 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1411 })?;
1412
1413 let res = CreateSubmarineResponse {
1414 id: self.id.clone(),
1415 accept_zero_conf: internal_create_response.accept_zero_conf,
1416 address: internal_create_response.address.clone(),
1417 bip21: internal_create_response.bip21.clone(),
1418 claim_public_key: crate::utils::json_to_pubkey(
1419 &internal_create_response.claim_public_key,
1420 )?,
1421 expected_amount: internal_create_response.expected_amount,
1422 referral_id: internal_create_response.referral_id,
1423 swap_tree: internal_create_response.swap_tree.clone().into(),
1424 timeout_block_height: internal_create_response.timeout_block_height,
1425 blinding_key: internal_create_response.blinding_key.clone(),
1426 };
1427 Ok(res)
1428 }
1429
1430 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1431 LBtcSwapScript::submarine_from_swap_resp(
1432 &self.get_boltz_create_response()?,
1433 self.get_refund_keypair()?.public_key().into(),
1434 )
1435 .map_err(|e| {
1436 SdkError::generic(format!(
1437 "Failed to create swap script for Send Swap {}: {e:?}",
1438 self.id
1439 ))
1440 })
1441 }
1442
1443 pub(crate) fn from_boltz_struct_to_json(
1444 create_response: &CreateSubmarineResponse,
1445 expected_swap_id: &str,
1446 ) -> Result<String, PaymentError> {
1447 let internal_create_response =
1448 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1449 create_response,
1450 expected_swap_id,
1451 )?;
1452
1453 let create_response_json =
1454 serde_json::to_string(&internal_create_response).map_err(|e| {
1455 PaymentError::Generic {
1456 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1457 }
1458 })?;
1459
1460 Ok(create_response_json)
1461 }
1462}
1463
1464#[derive(Clone, Debug, Derivative)]
1466#[derivative(PartialEq)]
1467pub struct ReceiveSwap {
1468 pub(crate) id: String,
1469 pub(crate) preimage: String,
1470 pub(crate) create_response_json: String,
1472 pub(crate) claim_private_key: String,
1473 pub(crate) invoice: String,
1474 pub(crate) bolt12_offer: Option<String>,
1476 pub(crate) payment_hash: Option<String>,
1477 pub(crate) destination_pubkey: Option<String>,
1478 pub(crate) description: Option<String>,
1479 pub(crate) payer_note: Option<String>,
1480 pub(crate) payer_amount_sat: u64,
1482 pub(crate) receiver_amount_sat: u64,
1483 pub(crate) pair_fees_json: String,
1485 pub(crate) claim_fees_sat: u64,
1486 pub(crate) claim_address: Option<String>,
1488 pub(crate) claim_tx_id: Option<String>,
1490 pub(crate) lockup_tx_id: Option<String>,
1492 pub(crate) mrh_address: String,
1494 pub(crate) mrh_tx_id: Option<String>,
1496 pub(crate) created_at: u32,
1499 pub(crate) timeout_block_height: u32,
1500 pub(crate) state: PaymentState,
1501 #[derivative(PartialEq = "ignore")]
1503 pub(crate) metadata: SwapMetadata,
1504}
1505impl ReceiveSwap {
1506 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1507 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1508 }
1509
1510 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1511 Ok(self
1512 .get_swap_script()?
1513 .funding_addrs
1514 .ok_or(anyhow!("No funding address found"))?
1515 .script_pubkey())
1516 }
1517
1518 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1519 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1520 serde_json::from_str(&self.create_response_json).map_err(|e| {
1521 PaymentError::Generic {
1522 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1523 }
1524 })?;
1525
1526 let res = CreateReverseResponse {
1527 id: self.id.clone(),
1528 invoice: Some(self.invoice.clone()),
1529 swap_tree: internal_create_response.swap_tree.clone().into(),
1530 lockup_address: internal_create_response.lockup_address.clone(),
1531 refund_public_key: crate::utils::json_to_pubkey(
1532 &internal_create_response.refund_public_key,
1533 )?,
1534 timeout_block_height: internal_create_response.timeout_block_height,
1535 onchain_amount: internal_create_response.onchain_amount,
1536 blinding_key: internal_create_response.blinding_key.clone(),
1537 };
1538 Ok(res)
1539 }
1540
1541 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1542 let keypair = self.get_claim_keypair()?;
1543 let create_response =
1544 self.get_boltz_create_response()
1545 .map_err(|e| PaymentError::Generic {
1546 err: format!(
1547 "Failed to create swap script for Receive Swap {}: {e:?}",
1548 self.id
1549 ),
1550 })?;
1551 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1552 .map_err(|e| PaymentError::Generic {
1553 err: format!(
1554 "Failed to create swap script for Receive Swap {}: {e:?}",
1555 self.id
1556 ),
1557 })
1558 }
1559
1560 pub(crate) fn from_boltz_struct_to_json(
1561 create_response: &CreateReverseResponse,
1562 expected_swap_id: &str,
1563 expected_invoice: Option<&str>,
1564 ) -> Result<String, PaymentError> {
1565 let internal_create_response =
1566 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1567 create_response,
1568 expected_swap_id,
1569 expected_invoice,
1570 )?;
1571
1572 let create_response_json =
1573 serde_json::to_string(&internal_create_response).map_err(|e| {
1574 PaymentError::Generic {
1575 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1576 }
1577 })?;
1578
1579 Ok(create_response_json)
1580 }
1581}
1582
1583#[derive(Clone, Debug, PartialEq, Serialize)]
1585pub struct RefundableSwap {
1586 pub swap_address: String,
1587 pub timestamp: u32,
1588 pub amount_sat: u64,
1590 pub last_refund_tx_id: Option<String>,
1592}
1593
1594#[derive(Clone, Debug, Derivative)]
1596#[derivative(PartialEq)]
1597pub(crate) struct Bolt12Offer {
1598 pub(crate) id: String,
1600 pub(crate) description: String,
1602 pub(crate) private_key: String,
1604 pub(crate) webhook_url: Option<String>,
1606 pub(crate) created_at: u32,
1608}
1609impl Bolt12Offer {
1610 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1611 utils::decode_keypair(&self.private_key)
1612 }
1613}
1614impl TryFrom<Bolt12Offer> for Offer {
1615 type Error = SdkError;
1616
1617 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1618 Offer::from_str(&val.id)
1619 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1620 }
1621}
1622
1623#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1625#[strum(serialize_all = "lowercase")]
1626pub enum PaymentState {
1627 #[default]
1628 Created = 0,
1629
1630 Pending = 1,
1650
1651 Complete = 2,
1663
1664 Failed = 3,
1672
1673 TimedOut = 4,
1678
1679 Refundable = 5,
1684
1685 RefundPending = 6,
1691
1692 WaitingFeeAcceptance = 7,
1704}
1705
1706impl ToSql for PaymentState {
1707 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1708 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1709 }
1710}
1711impl FromSql for PaymentState {
1712 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1713 match value {
1714 ValueRef::Integer(i) => match i as u8 {
1715 0 => Ok(PaymentState::Created),
1716 1 => Ok(PaymentState::Pending),
1717 2 => Ok(PaymentState::Complete),
1718 3 => Ok(PaymentState::Failed),
1719 4 => Ok(PaymentState::TimedOut),
1720 5 => Ok(PaymentState::Refundable),
1721 6 => Ok(PaymentState::RefundPending),
1722 7 => Ok(PaymentState::WaitingFeeAcceptance),
1723 _ => Err(FromSqlError::OutOfRange(i)),
1724 },
1725 _ => Err(FromSqlError::InvalidType),
1726 }
1727 }
1728}
1729
1730impl PaymentState {
1731 pub(crate) fn is_refundable(&self) -> bool {
1732 matches!(
1733 self,
1734 PaymentState::Refundable
1735 | PaymentState::RefundPending
1736 | PaymentState::WaitingFeeAcceptance
1737 )
1738 }
1739}
1740
1741#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1742#[strum(serialize_all = "lowercase")]
1743pub enum PaymentType {
1744 Receive = 0,
1745 Send = 1,
1746}
1747impl From<Direction> for PaymentType {
1748 fn from(value: Direction) -> Self {
1749 match value {
1750 Direction::Incoming => Self::Receive,
1751 Direction::Outgoing => Self::Send,
1752 }
1753 }
1754}
1755impl ToSql for PaymentType {
1756 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1757 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1758 }
1759}
1760impl FromSql for PaymentType {
1761 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1762 match value {
1763 ValueRef::Integer(i) => match i as u8 {
1764 0 => Ok(PaymentType::Receive),
1765 1 => Ok(PaymentType::Send),
1766 _ => Err(FromSqlError::OutOfRange(i)),
1767 },
1768 _ => Err(FromSqlError::InvalidType),
1769 }
1770 }
1771}
1772
1773#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1774pub enum PaymentStatus {
1775 Pending = 0,
1776 Complete = 1,
1777}
1778impl ToSql for PaymentStatus {
1779 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1780 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1781 }
1782}
1783impl FromSql for PaymentStatus {
1784 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1785 match value {
1786 ValueRef::Integer(i) => match i as u8 {
1787 0 => Ok(PaymentStatus::Pending),
1788 1 => Ok(PaymentStatus::Complete),
1789 _ => Err(FromSqlError::OutOfRange(i)),
1790 },
1791 _ => Err(FromSqlError::InvalidType),
1792 }
1793 }
1794}
1795
1796#[derive(Debug, Clone, Serialize)]
1797pub struct PaymentTxData {
1798 pub tx_id: String,
1800
1801 pub timestamp: Option<u32>,
1803
1804 pub fees_sat: u64,
1806
1807 pub is_confirmed: bool,
1809
1810 pub unblinding_data: Option<String>,
1813}
1814
1815#[derive(Debug, Clone, Serialize)]
1816pub enum PaymentSwapType {
1817 Receive,
1818 Send,
1819 Chain,
1820}
1821
1822#[derive(Debug, Clone, Serialize)]
1823pub struct PaymentSwapData {
1824 pub swap_id: String,
1825
1826 pub swap_type: PaymentSwapType,
1827
1828 pub created_at: u32,
1830
1831 pub expiration_blockheight: u32,
1834
1835 pub claim_expiration_blockheight: Option<u32>,
1837
1838 pub preimage: Option<String>,
1839 pub invoice: Option<String>,
1840 pub bolt12_offer: Option<String>,
1841 pub payment_hash: Option<String>,
1842 pub destination_pubkey: Option<String>,
1843 pub description: String,
1844 pub payer_note: Option<String>,
1845
1846 pub payer_amount_sat: u64,
1848
1849 pub receiver_amount_sat: u64,
1851
1852 pub swapper_fees_sat: u64,
1854
1855 pub refund_tx_id: Option<String>,
1856 pub refund_tx_amount_sat: Option<u64>,
1857
1858 pub bitcoin_address: Option<String>,
1861
1862 pub status: PaymentState,
1864}
1865
1866#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1868pub struct LnUrlInfo {
1869 pub ln_address: Option<String>,
1870 pub lnurl_pay_comment: Option<String>,
1871 pub lnurl_pay_domain: Option<String>,
1872 pub lnurl_pay_metadata: Option<String>,
1873 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1874 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1875 pub lnurl_withdraw_endpoint: Option<String>,
1876}
1877
1878#[derive(Debug, Clone, Serialize)]
1882pub struct AssetMetadata {
1883 pub asset_id: String,
1885 pub name: String,
1887 pub ticker: String,
1889 pub precision: u8,
1892 pub fiat_id: Option<String>,
1894}
1895
1896impl AssetMetadata {
1897 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1898 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1899 }
1900
1901 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1902 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1903 }
1904}
1905
1906#[derive(Clone, Debug, PartialEq, Serialize)]
1909pub struct AssetInfo {
1910 pub name: String,
1912 pub ticker: String,
1914 pub amount: f64,
1917 pub fees: Option<f64>,
1920}
1921
1922#[derive(Debug, Clone, PartialEq, Serialize)]
1924#[allow(clippy::large_enum_variant)]
1925pub enum PaymentDetails {
1926 Lightning {
1928 swap_id: String,
1929
1930 description: String,
1932
1933 liquid_expiration_blockheight: u32,
1935
1936 preimage: Option<String>,
1938
1939 invoice: Option<String>,
1943
1944 bolt12_offer: Option<String>,
1945
1946 payment_hash: Option<String>,
1948
1949 destination_pubkey: Option<String>,
1951
1952 lnurl_info: Option<LnUrlInfo>,
1954
1955 bip353_address: Option<String>,
1957
1958 payer_note: Option<String>,
1960
1961 claim_tx_id: Option<String>,
1963
1964 refund_tx_id: Option<String>,
1966
1967 refund_tx_amount_sat: Option<u64>,
1969
1970 settled_at: Option<u32>,
1972 },
1973 Liquid {
1975 destination: String,
1977
1978 description: String,
1980
1981 asset_id: String,
1983
1984 asset_info: Option<AssetInfo>,
1986
1987 lnurl_info: Option<LnUrlInfo>,
1989
1990 bip353_address: Option<String>,
1992
1993 payer_note: Option<String>,
1995 },
1996 Bitcoin {
1998 swap_id: String,
1999
2000 bitcoin_address: String,
2002
2003 description: String,
2005
2006 auto_accepted_fees: bool,
2010
2011 liquid_expiration_blockheight: u32,
2013
2014 bitcoin_expiration_blockheight: u32,
2016
2017 lockup_tx_id: Option<String>,
2019
2020 claim_tx_id: Option<String>,
2022
2023 refund_tx_id: Option<String>,
2025
2026 refund_tx_amount_sat: Option<u64>,
2028 },
2029}
2030
2031impl PaymentDetails {
2032 pub(crate) fn get_swap_id(&self) -> Option<String> {
2033 match self {
2034 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2035 Some(swap_id.clone())
2036 }
2037 Self::Liquid { .. } => None,
2038 }
2039 }
2040
2041 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2042 match self {
2043 Self::Lightning {
2044 refund_tx_amount_sat,
2045 ..
2046 }
2047 | Self::Bitcoin {
2048 refund_tx_amount_sat,
2049 ..
2050 } => *refund_tx_amount_sat,
2051 Self::Liquid { .. } => None,
2052 }
2053 }
2054
2055 pub(crate) fn get_description(&self) -> Option<String> {
2056 match self {
2057 Self::Lightning { description, .. }
2058 | Self::Bitcoin { description, .. }
2059 | Self::Liquid { description, .. } => Some(description.clone()),
2060 }
2061 }
2062
2063 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2064 match self {
2065 Self::Liquid { asset_id, .. } => {
2066 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2067 }
2068 _ => true,
2069 }
2070 }
2071}
2072
2073#[derive(Debug, Clone, PartialEq, Serialize)]
2077pub struct Payment {
2078 pub destination: Option<String>,
2081
2082 pub tx_id: Option<String>,
2083
2084 pub unblinding_data: Option<String>,
2087
2088 pub timestamp: u32,
2094
2095 pub amount_sat: u64,
2099
2100 pub fees_sat: u64,
2114
2115 pub swapper_fees_sat: Option<u64>,
2118
2119 pub payment_type: PaymentType,
2121
2122 pub status: PaymentState,
2128
2129 pub details: PaymentDetails,
2132}
2133impl Payment {
2134 pub(crate) fn from_pending_swap(
2135 swap: PaymentSwapData,
2136 payment_type: PaymentType,
2137 payment_details: PaymentDetails,
2138 ) -> Payment {
2139 let amount_sat = match payment_type {
2140 PaymentType::Receive => swap.receiver_amount_sat,
2141 PaymentType::Send => swap.payer_amount_sat,
2142 };
2143
2144 Payment {
2145 destination: swap.invoice.clone(),
2146 tx_id: None,
2147 unblinding_data: None,
2148 timestamp: swap.created_at,
2149 amount_sat,
2150 fees_sat: swap
2151 .payer_amount_sat
2152 .saturating_sub(swap.receiver_amount_sat),
2153 swapper_fees_sat: Some(swap.swapper_fees_sat),
2154 payment_type,
2155 status: swap.status,
2156 details: payment_details,
2157 }
2158 }
2159
2160 pub(crate) fn from_tx_data(
2161 tx: PaymentTxData,
2162 balance: PaymentTxBalance,
2163 swap: Option<PaymentSwapData>,
2164 details: PaymentDetails,
2165 ) -> Payment {
2166 let (amount_sat, fees_sat) = match swap.as_ref() {
2167 Some(s) => match balance.payment_type {
2168 PaymentType::Receive => (
2172 balance.amount,
2173 s.payer_amount_sat.saturating_sub(balance.amount),
2174 ),
2175 PaymentType::Send => (
2176 s.receiver_amount_sat,
2177 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2178 ),
2179 },
2180 None => {
2181 let (amount_sat, fees_sat) = match balance.payment_type {
2182 PaymentType::Receive => (balance.amount, 0),
2183 PaymentType::Send => (balance.amount, tx.fees_sat),
2184 };
2185 match details {
2188 PaymentDetails::Liquid {
2189 asset_info: Some(ref asset_info),
2190 ..
2191 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2192 _ => (amount_sat, fees_sat),
2193 }
2194 }
2195 };
2196 Payment {
2197 tx_id: Some(tx.tx_id),
2198 unblinding_data: tx.unblinding_data,
2199 destination: match &swap {
2203 Some(PaymentSwapData {
2204 swap_type: PaymentSwapType::Receive,
2205 invoice,
2206 ..
2207 }) => invoice.clone(),
2208 Some(PaymentSwapData {
2209 swap_type: PaymentSwapType::Send,
2210 invoice,
2211 bolt12_offer,
2212 ..
2213 }) => bolt12_offer.clone().or(invoice.clone()),
2214 Some(PaymentSwapData {
2215 swap_type: PaymentSwapType::Chain,
2216 bitcoin_address,
2217 ..
2218 }) => bitcoin_address.clone(),
2219 _ => match &details {
2220 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2221 _ => None,
2222 },
2223 },
2224 timestamp: tx
2225 .timestamp
2226 .or(swap.as_ref().map(|s| s.created_at))
2227 .unwrap_or(utils::now()),
2228 amount_sat,
2229 fees_sat,
2230 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2231 payment_type: balance.payment_type,
2232 status: match &swap {
2233 Some(swap) => swap.status,
2234 None => match tx.is_confirmed {
2235 true => PaymentState::Complete,
2236 false => PaymentState::Pending,
2237 },
2238 },
2239 details,
2240 }
2241 }
2242
2243 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2244 match self.details.clone() {
2245 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2246 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2247 PaymentDetails::Liquid { .. } => None,
2248 }
2249 .flatten()
2250 }
2251}
2252
2253#[derive(Deserialize, Serialize, Clone, Debug)]
2255#[serde(rename_all = "camelCase")]
2256pub struct RecommendedFees {
2257 pub fastest_fee: u64,
2258 pub half_hour_fee: u64,
2259 pub hour_fee: u64,
2260 pub economy_fee: u64,
2261 pub minimum_fee: u64,
2262}
2263
2264#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2266pub enum BuyBitcoinProvider {
2267 #[strum(serialize = "moonpay")]
2268 Moonpay,
2269}
2270
2271#[derive(Debug, Serialize)]
2273pub struct PrepareBuyBitcoinRequest {
2274 pub provider: BuyBitcoinProvider,
2275 pub amount_sat: u64,
2276}
2277
2278#[derive(Clone, Debug, Serialize)]
2280pub struct PrepareBuyBitcoinResponse {
2281 pub provider: BuyBitcoinProvider,
2282 pub amount_sat: u64,
2283 pub fees_sat: u64,
2284}
2285
2286#[derive(Clone, Debug, Serialize)]
2288pub struct BuyBitcoinRequest {
2289 pub prepare_response: PrepareBuyBitcoinResponse,
2290 pub redirect_url: Option<String>,
2294}
2295
2296#[derive(Clone, Debug)]
2298pub struct LogEntry {
2299 pub line: String,
2300 pub level: String,
2301}
2302
2303#[derive(Clone, Debug, Serialize, Deserialize)]
2304struct InternalLeaf {
2305 pub output: String,
2306 pub version: u8,
2307}
2308impl From<InternalLeaf> for Leaf {
2309 fn from(value: InternalLeaf) -> Self {
2310 Leaf {
2311 output: value.output,
2312 version: value.version,
2313 }
2314 }
2315}
2316impl From<Leaf> for InternalLeaf {
2317 fn from(value: Leaf) -> Self {
2318 InternalLeaf {
2319 output: value.output,
2320 version: value.version,
2321 }
2322 }
2323}
2324
2325#[derive(Clone, Debug, Serialize, Deserialize)]
2326pub(super) struct InternalSwapTree {
2327 claim_leaf: InternalLeaf,
2328 refund_leaf: InternalLeaf,
2329}
2330impl From<InternalSwapTree> for SwapTree {
2331 fn from(value: InternalSwapTree) -> Self {
2332 SwapTree {
2333 claim_leaf: value.claim_leaf.into(),
2334 refund_leaf: value.refund_leaf.into(),
2335 }
2336 }
2337}
2338impl From<SwapTree> for InternalSwapTree {
2339 fn from(value: SwapTree) -> Self {
2340 InternalSwapTree {
2341 claim_leaf: value.claim_leaf.into(),
2342 refund_leaf: value.refund_leaf.into(),
2343 }
2344 }
2345}
2346
2347#[derive(Debug, Serialize)]
2349pub struct PrepareLnUrlPayRequest {
2350 pub data: LnUrlPayRequestData,
2352 pub amount: PayAmount,
2354 pub bip353_address: Option<String>,
2357 pub comment: Option<String>,
2360 pub validate_success_action_url: Option<bool>,
2363}
2364
2365#[derive(Debug, Serialize)]
2367pub struct PrepareLnUrlPayResponse {
2368 pub destination: SendDestination,
2370 pub fees_sat: u64,
2372 pub data: LnUrlPayRequestData,
2374 pub amount: PayAmount,
2376 pub comment: Option<String>,
2379 pub success_action: Option<SuccessAction>,
2382}
2383
2384#[derive(Debug, Serialize)]
2386pub struct LnUrlPayRequest {
2387 pub prepare_response: PrepareLnUrlPayResponse,
2389}
2390
2391#[derive(Serialize)]
2403#[allow(clippy::large_enum_variant)]
2404pub enum LnUrlPayResult {
2405 EndpointSuccess { data: LnUrlPaySuccessData },
2406 EndpointError { data: LnUrlErrorData },
2407 PayError { data: LnUrlPayErrorData },
2408}
2409
2410#[derive(Serialize)]
2411pub struct LnUrlPaySuccessData {
2412 pub payment: Payment,
2413 pub success_action: Option<SuccessActionProcessed>,
2414}
2415
2416#[derive(Debug, Clone)]
2417pub enum Transaction {
2418 Liquid(boltz_client::elements::Transaction),
2419 Bitcoin(boltz_client::bitcoin::Transaction),
2420}
2421
2422impl Transaction {
2423 pub(crate) fn txid(&self) -> String {
2424 match self {
2425 Transaction::Liquid(tx) => tx.txid().to_hex(),
2426 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2427 }
2428 }
2429}
2430
2431#[derive(Debug, Clone)]
2432pub enum Utxo {
2433 Liquid(
2434 Box<(
2435 boltz_client::elements::OutPoint,
2436 boltz_client::elements::TxOut,
2437 )>,
2438 ),
2439 Bitcoin(
2440 (
2441 boltz_client::bitcoin::OutPoint,
2442 boltz_client::bitcoin::TxOut,
2443 ),
2444 ),
2445}
2446
2447impl Utxo {
2448 pub(crate) fn as_bitcoin(
2449 &self,
2450 ) -> Option<&(
2451 boltz_client::bitcoin::OutPoint,
2452 boltz_client::bitcoin::TxOut,
2453 )> {
2454 match self {
2455 Utxo::Liquid(_) => None,
2456 Utxo::Bitcoin(utxo) => Some(utxo),
2457 }
2458 }
2459
2460 pub(crate) fn as_liquid(
2461 &self,
2462 ) -> Option<
2463 Box<(
2464 boltz_client::elements::OutPoint,
2465 boltz_client::elements::TxOut,
2466 )>,
2467 > {
2468 match self {
2469 Utxo::Bitcoin(_) => None,
2470 Utxo::Liquid(utxo) => Some(utxo.clone()),
2471 }
2472 }
2473}
2474
2475#[derive(Debug, Clone)]
2477pub struct FetchPaymentProposedFeesRequest {
2478 pub swap_id: String,
2479}
2480
2481#[derive(Debug, Clone, Serialize)]
2483pub struct FetchPaymentProposedFeesResponse {
2484 pub swap_id: String,
2485 pub fees_sat: u64,
2486 pub payer_amount_sat: u64,
2488 pub receiver_amount_sat: u64,
2490}
2491
2492#[derive(Debug, Clone)]
2494pub struct AcceptPaymentProposedFeesRequest {
2495 pub response: FetchPaymentProposedFeesResponse,
2496}
2497
2498#[derive(Clone, Debug)]
2499pub struct History<T> {
2500 pub txid: T,
2501 pub height: i32,
2506}
2507pub(crate) type LBtcHistory = History<elements::Txid>;
2508pub(crate) type BtcHistory = History<bitcoin::Txid>;
2509
2510impl<T> History<T> {
2511 pub(crate) fn confirmed(&self) -> bool {
2512 self.height > 0
2513 }
2514}
2515#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2516impl From<electrum_client::GetHistoryRes> for BtcHistory {
2517 fn from(value: electrum_client::GetHistoryRes) -> Self {
2518 Self {
2519 txid: value.tx_hash,
2520 height: value.height,
2521 }
2522 }
2523}
2524impl From<lwk_wollet::History> for LBtcHistory {
2525 fn from(value: lwk_wollet::History) -> Self {
2526 Self::from(&value)
2527 }
2528}
2529impl From<&lwk_wollet::History> for LBtcHistory {
2530 fn from(value: &lwk_wollet::History) -> Self {
2531 Self {
2532 txid: value.txid,
2533 height: value.height,
2534 }
2535 }
2536}
2537pub(crate) type BtcScript = bitcoin::ScriptBuf;
2538pub(crate) type LBtcScript = elements::Script;
2539
2540#[derive(Clone, Debug)]
2541pub struct BtcScriptBalance {
2542 pub confirmed: u64,
2544 pub unconfirmed: i64,
2548}
2549#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2550impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2551 fn from(val: electrum_client::GetBalanceRes) -> Self {
2552 Self {
2553 confirmed: val.confirmed,
2554 unconfirmed: val.unconfirmed,
2555 }
2556 }
2557}
2558
2559pub(crate) struct GetSyncContextRequest {
2560 pub partial_sync: Option<bool>,
2561 pub last_liquid_tip: u32,
2562 pub last_bitcoin_tip: u32,
2563}
2564
2565pub(crate) struct SyncContext {
2566 pub maybe_liquid_tip: Option<u32>,
2567 pub maybe_bitcoin_tip: Option<u32>,
2568 pub recoverable_swaps: Vec<Swap>,
2569 pub is_new_liquid_block: bool,
2570 pub is_new_bitcoin_block: bool,
2571}
2572
2573pub(crate) struct TaskHandle {
2574 pub name: String,
2575 pub handle: tokio::task::JoinHandle<()>,
2576}
2577
2578#[macro_export]
2579macro_rules! get_updated_fields {
2580 ($($var:ident),* $(,)?) => {{
2581 let mut options = Vec::new();
2582 $(
2583 if $var.is_some() {
2584 options.push(stringify!($var).to_string());
2585 }
2586 )*
2587 match options.len() > 0 {
2588 true => Some(options),
2589 false => None,
2590 }
2591 }};
2592}