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