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 settled_at: Option<u32>,
1968 },
1969 Liquid {
1971 destination: String,
1973
1974 description: String,
1976
1977 asset_id: String,
1979
1980 asset_info: Option<AssetInfo>,
1982
1983 lnurl_info: Option<LnUrlInfo>,
1985
1986 bip353_address: Option<String>,
1988
1989 payer_note: Option<String>,
1991 },
1992 Bitcoin {
1994 swap_id: String,
1995
1996 bitcoin_address: String,
1998
1999 description: String,
2001
2002 auto_accepted_fees: bool,
2006
2007 liquid_expiration_blockheight: u32,
2009
2010 bitcoin_expiration_blockheight: u32,
2012
2013 lockup_tx_id: Option<String>,
2015
2016 claim_tx_id: Option<String>,
2018
2019 refund_tx_id: Option<String>,
2021
2022 refund_tx_amount_sat: Option<u64>,
2024 },
2025}
2026
2027impl PaymentDetails {
2028 pub(crate) fn get_swap_id(&self) -> Option<String> {
2029 match self {
2030 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2031 Some(swap_id.clone())
2032 }
2033 Self::Liquid { .. } => None,
2034 }
2035 }
2036
2037 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2038 match self {
2039 Self::Lightning {
2040 refund_tx_amount_sat,
2041 ..
2042 }
2043 | Self::Bitcoin {
2044 refund_tx_amount_sat,
2045 ..
2046 } => *refund_tx_amount_sat,
2047 Self::Liquid { .. } => None,
2048 }
2049 }
2050
2051 pub(crate) fn get_description(&self) -> Option<String> {
2052 match self {
2053 Self::Lightning { description, .. }
2054 | Self::Bitcoin { description, .. }
2055 | Self::Liquid { description, .. } => Some(description.clone()),
2056 }
2057 }
2058
2059 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2060 match self {
2061 Self::Liquid { asset_id, .. } => {
2062 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2063 }
2064 _ => true,
2065 }
2066 }
2067}
2068
2069#[derive(Debug, Clone, PartialEq, Serialize)]
2073pub struct Payment {
2074 pub destination: Option<String>,
2077
2078 pub tx_id: Option<String>,
2079
2080 pub unblinding_data: Option<String>,
2083
2084 pub timestamp: u32,
2090
2091 pub amount_sat: u64,
2095
2096 pub fees_sat: u64,
2110
2111 pub swapper_fees_sat: Option<u64>,
2114
2115 pub payment_type: PaymentType,
2117
2118 pub status: PaymentState,
2124
2125 pub details: PaymentDetails,
2128}
2129impl Payment {
2130 pub(crate) fn from_pending_swap(
2131 swap: PaymentSwapData,
2132 payment_type: PaymentType,
2133 payment_details: PaymentDetails,
2134 ) -> Payment {
2135 let amount_sat = match payment_type {
2136 PaymentType::Receive => swap.receiver_amount_sat,
2137 PaymentType::Send => swap.payer_amount_sat,
2138 };
2139
2140 Payment {
2141 destination: swap.invoice.clone(),
2142 tx_id: None,
2143 unblinding_data: None,
2144 timestamp: swap.created_at,
2145 amount_sat,
2146 fees_sat: swap
2147 .payer_amount_sat
2148 .saturating_sub(swap.receiver_amount_sat),
2149 swapper_fees_sat: Some(swap.swapper_fees_sat),
2150 payment_type,
2151 status: swap.status,
2152 details: payment_details,
2153 }
2154 }
2155
2156 pub(crate) fn from_tx_data(
2157 tx: PaymentTxData,
2158 balance: PaymentTxBalance,
2159 swap: Option<PaymentSwapData>,
2160 details: PaymentDetails,
2161 ) -> Payment {
2162 let (amount_sat, fees_sat) = match swap.as_ref() {
2163 Some(s) => match balance.payment_type {
2164 PaymentType::Receive => (
2168 balance.amount,
2169 s.payer_amount_sat.saturating_sub(balance.amount),
2170 ),
2171 PaymentType::Send => (
2172 s.receiver_amount_sat,
2173 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2174 ),
2175 },
2176 None => {
2177 let (amount_sat, fees_sat) = match balance.payment_type {
2178 PaymentType::Receive => (balance.amount, 0),
2179 PaymentType::Send => (balance.amount, tx.fees_sat),
2180 };
2181 match details {
2184 PaymentDetails::Liquid {
2185 asset_info: Some(ref asset_info),
2186 ..
2187 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2188 _ => (amount_sat, fees_sat),
2189 }
2190 }
2191 };
2192 Payment {
2193 tx_id: Some(tx.tx_id),
2194 unblinding_data: tx.unblinding_data,
2195 destination: match &swap {
2199 Some(PaymentSwapData {
2200 swap_type: PaymentSwapType::Receive,
2201 invoice,
2202 ..
2203 }) => invoice.clone(),
2204 Some(PaymentSwapData {
2205 swap_type: PaymentSwapType::Send,
2206 invoice,
2207 bolt12_offer,
2208 ..
2209 }) => bolt12_offer.clone().or(invoice.clone()),
2210 Some(PaymentSwapData {
2211 swap_type: PaymentSwapType::Chain,
2212 bitcoin_address,
2213 ..
2214 }) => bitcoin_address.clone(),
2215 _ => match &details {
2216 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2217 _ => None,
2218 },
2219 },
2220 timestamp: tx
2221 .timestamp
2222 .or(swap.as_ref().map(|s| s.created_at))
2223 .unwrap_or(utils::now()),
2224 amount_sat,
2225 fees_sat,
2226 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2227 payment_type: balance.payment_type,
2228 status: match &swap {
2229 Some(swap) => swap.status,
2230 None => match tx.is_confirmed {
2231 true => PaymentState::Complete,
2232 false => PaymentState::Pending,
2233 },
2234 },
2235 details,
2236 }
2237 }
2238
2239 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2240 match self.details.clone() {
2241 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2242 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2243 PaymentDetails::Liquid { .. } => None,
2244 }
2245 .flatten()
2246 }
2247}
2248
2249#[derive(Deserialize, Serialize, Clone, Debug)]
2251#[serde(rename_all = "camelCase")]
2252pub struct RecommendedFees {
2253 pub fastest_fee: u64,
2254 pub half_hour_fee: u64,
2255 pub hour_fee: u64,
2256 pub economy_fee: u64,
2257 pub minimum_fee: u64,
2258}
2259
2260#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2262pub enum BuyBitcoinProvider {
2263 #[strum(serialize = "moonpay")]
2264 Moonpay,
2265}
2266
2267#[derive(Debug, Serialize)]
2269pub struct PrepareBuyBitcoinRequest {
2270 pub provider: BuyBitcoinProvider,
2271 pub amount_sat: u64,
2272}
2273
2274#[derive(Clone, Debug, Serialize)]
2276pub struct PrepareBuyBitcoinResponse {
2277 pub provider: BuyBitcoinProvider,
2278 pub amount_sat: u64,
2279 pub fees_sat: u64,
2280}
2281
2282#[derive(Clone, Debug, Serialize)]
2284pub struct BuyBitcoinRequest {
2285 pub prepare_response: PrepareBuyBitcoinResponse,
2286 pub redirect_url: Option<String>,
2290}
2291
2292#[derive(Clone, Debug)]
2294pub struct LogEntry {
2295 pub line: String,
2296 pub level: String,
2297}
2298
2299#[derive(Clone, Debug, Serialize, Deserialize)]
2300struct InternalLeaf {
2301 pub output: String,
2302 pub version: u8,
2303}
2304impl From<InternalLeaf> for Leaf {
2305 fn from(value: InternalLeaf) -> Self {
2306 Leaf {
2307 output: value.output,
2308 version: value.version,
2309 }
2310 }
2311}
2312impl From<Leaf> for InternalLeaf {
2313 fn from(value: Leaf) -> Self {
2314 InternalLeaf {
2315 output: value.output,
2316 version: value.version,
2317 }
2318 }
2319}
2320
2321#[derive(Clone, Debug, Serialize, Deserialize)]
2322pub(super) struct InternalSwapTree {
2323 claim_leaf: InternalLeaf,
2324 refund_leaf: InternalLeaf,
2325}
2326impl From<InternalSwapTree> for SwapTree {
2327 fn from(value: InternalSwapTree) -> Self {
2328 SwapTree {
2329 claim_leaf: value.claim_leaf.into(),
2330 refund_leaf: value.refund_leaf.into(),
2331 }
2332 }
2333}
2334impl From<SwapTree> for InternalSwapTree {
2335 fn from(value: SwapTree) -> Self {
2336 InternalSwapTree {
2337 claim_leaf: value.claim_leaf.into(),
2338 refund_leaf: value.refund_leaf.into(),
2339 }
2340 }
2341}
2342
2343#[derive(Debug, Serialize)]
2345pub struct PrepareLnUrlPayRequest {
2346 pub data: LnUrlPayRequestData,
2348 pub amount: PayAmount,
2350 pub bip353_address: Option<String>,
2353 pub comment: Option<String>,
2356 pub validate_success_action_url: Option<bool>,
2359}
2360
2361#[derive(Debug, Serialize)]
2363pub struct PrepareLnUrlPayResponse {
2364 pub destination: SendDestination,
2366 pub fees_sat: u64,
2368 pub data: LnUrlPayRequestData,
2370 pub amount: PayAmount,
2372 pub comment: Option<String>,
2375 pub success_action: Option<SuccessAction>,
2378}
2379
2380#[derive(Debug, Serialize)]
2382pub struct LnUrlPayRequest {
2383 pub prepare_response: PrepareLnUrlPayResponse,
2385}
2386
2387#[derive(Serialize)]
2399#[allow(clippy::large_enum_variant)]
2400pub enum LnUrlPayResult {
2401 EndpointSuccess { data: LnUrlPaySuccessData },
2402 EndpointError { data: LnUrlErrorData },
2403 PayError { data: LnUrlPayErrorData },
2404}
2405
2406#[derive(Serialize)]
2407pub struct LnUrlPaySuccessData {
2408 pub payment: Payment,
2409 pub success_action: Option<SuccessActionProcessed>,
2410}
2411
2412#[derive(Debug, Clone)]
2413pub enum Transaction {
2414 Liquid(boltz_client::elements::Transaction),
2415 Bitcoin(boltz_client::bitcoin::Transaction),
2416}
2417
2418impl Transaction {
2419 pub(crate) fn txid(&self) -> String {
2420 match self {
2421 Transaction::Liquid(tx) => tx.txid().to_hex(),
2422 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2423 }
2424 }
2425}
2426
2427#[derive(Debug, Clone)]
2428pub enum Utxo {
2429 Liquid(
2430 Box<(
2431 boltz_client::elements::OutPoint,
2432 boltz_client::elements::TxOut,
2433 )>,
2434 ),
2435 Bitcoin(
2436 (
2437 boltz_client::bitcoin::OutPoint,
2438 boltz_client::bitcoin::TxOut,
2439 ),
2440 ),
2441}
2442
2443impl Utxo {
2444 pub(crate) fn as_bitcoin(
2445 &self,
2446 ) -> Option<&(
2447 boltz_client::bitcoin::OutPoint,
2448 boltz_client::bitcoin::TxOut,
2449 )> {
2450 match self {
2451 Utxo::Liquid(_) => None,
2452 Utxo::Bitcoin(utxo) => Some(utxo),
2453 }
2454 }
2455
2456 pub(crate) fn as_liquid(
2457 &self,
2458 ) -> Option<
2459 Box<(
2460 boltz_client::elements::OutPoint,
2461 boltz_client::elements::TxOut,
2462 )>,
2463 > {
2464 match self {
2465 Utxo::Bitcoin(_) => None,
2466 Utxo::Liquid(utxo) => Some(utxo.clone()),
2467 }
2468 }
2469}
2470
2471#[derive(Debug, Clone)]
2473pub struct FetchPaymentProposedFeesRequest {
2474 pub swap_id: String,
2475}
2476
2477#[derive(Debug, Clone, Serialize)]
2479pub struct FetchPaymentProposedFeesResponse {
2480 pub swap_id: String,
2481 pub fees_sat: u64,
2482 pub payer_amount_sat: u64,
2484 pub receiver_amount_sat: u64,
2486}
2487
2488#[derive(Debug, Clone)]
2490pub struct AcceptPaymentProposedFeesRequest {
2491 pub response: FetchPaymentProposedFeesResponse,
2492}
2493
2494#[derive(Clone, Debug)]
2495pub struct History<T> {
2496 pub txid: T,
2497 pub height: i32,
2502}
2503pub(crate) type LBtcHistory = History<elements::Txid>;
2504pub(crate) type BtcHistory = History<bitcoin::Txid>;
2505
2506impl<T> History<T> {
2507 pub(crate) fn confirmed(&self) -> bool {
2508 self.height > 0
2509 }
2510}
2511#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2512impl From<electrum_client::GetHistoryRes> for BtcHistory {
2513 fn from(value: electrum_client::GetHistoryRes) -> Self {
2514 Self {
2515 txid: value.tx_hash,
2516 height: value.height,
2517 }
2518 }
2519}
2520impl From<lwk_wollet::History> for LBtcHistory {
2521 fn from(value: lwk_wollet::History) -> Self {
2522 Self::from(&value)
2523 }
2524}
2525impl From<&lwk_wollet::History> for LBtcHistory {
2526 fn from(value: &lwk_wollet::History) -> Self {
2527 Self {
2528 txid: value.txid,
2529 height: value.height,
2530 }
2531 }
2532}
2533pub(crate) type BtcScript = bitcoin::ScriptBuf;
2534pub(crate) type LBtcScript = elements::Script;
2535
2536#[derive(Clone, Debug)]
2537pub struct BtcScriptBalance {
2538 pub confirmed: u64,
2540 pub unconfirmed: i64,
2544}
2545#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2546impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2547 fn from(val: electrum_client::GetBalanceRes) -> Self {
2548 Self {
2549 confirmed: val.confirmed,
2550 unconfirmed: val.unconfirmed,
2551 }
2552 }
2553}
2554
2555pub(crate) struct GetSyncContextRequest {
2556 pub partial_sync: Option<bool>,
2557 pub last_liquid_tip: u32,
2558 pub last_bitcoin_tip: u32,
2559}
2560
2561pub(crate) struct SyncContext {
2562 pub maybe_liquid_tip: Option<u32>,
2563 pub maybe_bitcoin_tip: Option<u32>,
2564 pub recoverable_swaps: Vec<Swap>,
2565 pub is_new_liquid_block: bool,
2566 pub is_new_bitcoin_block: bool,
2567}
2568
2569pub(crate) struct TaskHandle {
2570 pub name: String,
2571 pub handle: tokio::task::JoinHandle<()>,
2572}
2573
2574#[macro_export]
2575macro_rules! get_updated_fields {
2576 ($($var:ident),* $(,)?) => {{
2577 let mut options = Vec::new();
2578 $(
2579 if $var.is_some() {
2580 options.push(stringify!($var).to_string());
2581 }
2582 )*
2583 match options.len() > 0 {
2584 true => Some(options),
2585 false => None,
2586 }
2587 }};
2588}