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 testnet(breez_api_key: Option<String>) -> Self {
169 Config {
170 liquid_explorer: BlockchainExplorer::Electrum {
171 url: "elements-testnet.blockstream.info:50002".to_string(),
172 },
173 bitcoin_explorer: BlockchainExplorer::Electrum {
174 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
175 },
176 working_dir: ".".to_string(),
177 network: LiquidNetwork::Testnet,
178 payment_timeout_sec: 15,
179 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
180 zero_conf_max_amount_sat: None,
181 breez_api_key,
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: Some(SIDESWAP_API_KEY.to_string()),
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 testnet_esplora(breez_api_key: Option<String>) -> Self {
194 Config {
195 liquid_explorer: BlockchainExplorer::Esplora {
196 url: "https://blockstream.info/liquidtestnet/api".to_string(),
197 use_waterfalls: false,
198 },
199 bitcoin_explorer: BlockchainExplorer::Esplora {
200 url: "https://blockstream.info/testnet/api/".to_string(),
201 use_waterfalls: false,
202 },
203 working_dir: ".".to_string(),
204 network: LiquidNetwork::Testnet,
205 payment_timeout_sec: 15,
206 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
207 zero_conf_max_amount_sat: None,
208 breez_api_key,
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: Some(SIDESWAP_API_KEY.to_string()),
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 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
221 pub fn regtest() -> Self {
222 Config {
223 liquid_explorer: BlockchainExplorer::Electrum {
224 url: "localhost:19002".to_string(),
225 },
226 bitcoin_explorer: BlockchainExplorer::Electrum {
227 url: "localhost:19001".to_string(),
228 },
229 working_dir: ".".to_string(),
230 network: LiquidNetwork::Regtest,
231 payment_timeout_sec: 15,
232 sync_service_url: Some("http://localhost:8088".to_string()),
233 zero_conf_max_amount_sat: None,
234 breez_api_key: None,
235 external_input_parsers: None,
236 use_default_external_input_parsers: true,
237 onchain_fee_rate_leeway_sat: None,
238 asset_metadata: None,
239 sideswap_api_key: None,
240 use_magic_routing_hints: true,
241 onchain_sync_period_sec: DEFAULT_ONCHAIN_SYNC_PERIOD_SEC,
242 onchain_sync_request_timeout_sec: DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC,
243 }
244 }
245
246 pub fn regtest_esplora() -> Self {
247 Config {
248 liquid_explorer: BlockchainExplorer::Esplora {
249 url: "http://localhost:3120/api".to_string(),
250 use_waterfalls: true,
251 },
252 bitcoin_explorer: BlockchainExplorer::Esplora {
253 url: "http://localhost:4002/api".to_string(),
254 use_waterfalls: false,
255 },
256 working_dir: ".".to_string(),
257 network: LiquidNetwork::Regtest,
258 payment_timeout_sec: 15,
259 sync_service_url: Some("http://localhost:8089".to_string()),
260 zero_conf_max_amount_sat: None,
261 breez_api_key: None,
262 external_input_parsers: None,
263 use_default_external_input_parsers: true,
264 onchain_fee_rate_leeway_sat: None,
265 asset_metadata: None,
266 sideswap_api_key: None,
267 use_magic_routing_hints: true,
268 onchain_sync_period_sec: DEFAULT_ONCHAIN_SYNC_PERIOD_SEC,
269 onchain_sync_request_timeout_sec: DEFAULT_ONCHAIN_SYNC_REQUEST_TIMEOUT_SEC,
270 }
271 }
272
273 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
274 Ok(PathBuf::from(base_dir)
275 .join(match self.network {
276 LiquidNetwork::Mainnet => "mainnet",
277 LiquidNetwork::Testnet => "testnet",
278 LiquidNetwork::Regtest => "regtest",
279 })
280 .join(fingerprint_hex)
281 .to_str()
282 .ok_or(anyhow::anyhow!(
283 "Could not get retrieve current wallet directory"
284 ))?
285 .to_string())
286 }
287
288 pub fn zero_conf_max_amount_sat(&self) -> u64 {
289 self.zero_conf_max_amount_sat
290 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
291 }
292
293 pub(crate) fn lbtc_asset_id(&self) -> String {
294 utils::lbtc_asset_id(self.network).to_string()
295 }
296
297 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
298 let mut external_input_parsers = Vec::new();
299 if self.use_default_external_input_parsers {
300 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
301 .iter()
302 .map(|(id, regex, url)| ExternalInputParser {
303 provider_id: id.to_string(),
304 input_regex: regex.to_string(),
305 parser_url: url.to_string(),
306 })
307 .collect::<Vec<_>>();
308 external_input_parsers.extend(default_parsers);
309 }
310 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
311
312 external_input_parsers
313 }
314
315 pub(crate) fn default_boltz_url(&self) -> &str {
316 match self.network {
317 LiquidNetwork::Mainnet => {
318 if self.breez_api_key.is_some() {
319 BREEZ_SWAP_PROXY_URL
320 } else {
321 BOLTZ_MAINNET_URL_V2
322 }
323 }
324 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
325 LiquidNetwork::Regtest => "http://localhost:8387/v2",
327 }
328 }
329
330 pub fn sync_enabled(&self) -> bool {
331 self.sync_service_url.is_some()
332 }
333
334 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
335 match self.bitcoin_explorer {
336 BlockchainExplorer::Esplora { .. } => {
337 Arc::new(EsploraBitcoinChainService::new(self.clone()))
338 }
339 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
340 BlockchainExplorer::Electrum { .. } => Arc::new(
341 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
342 ),
343 }
344 }
345
346 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
347 match &self.liquid_explorer {
348 BlockchainExplorer::Esplora { url, .. } => {
349 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
350 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")
351 }
352 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
353 }
354 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
355 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
356 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
357 )),
358 }
359 }
360
361 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
362 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
363 match self.network {
364 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
365 LiquidNetwork::Regtest => (false, false),
366 }
367 }
368
369 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
370 pub(crate) fn electrum_client(
371 &self,
372 url: &str,
373 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
374 let (tls, validate_domain) = self.electrum_tls_options();
375 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
376 lwk_wollet::ElectrumClient::with_options(
377 &electrum_url,
378 lwk_wollet::ElectrumOptions {
379 timeout: Some(self.onchain_sync_request_timeout_sec as u8),
380 },
381 )
382 }
383
384 pub(crate) fn sideswap_url(&self) -> &'static str {
385 match self.network {
386 LiquidNetwork::Mainnet => SIDESWAP_MAINNET_URL,
387 LiquidNetwork::Testnet => SIDESWAP_TESTNET_URL,
388 LiquidNetwork::Regtest => unimplemented!(),
389 }
390 }
391}
392
393#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
396pub enum LiquidNetwork {
397 Mainnet,
399 Testnet,
401 Regtest,
403}
404impl LiquidNetwork {
405 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
406 match self {
407 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
408 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
409 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
410 }
411 }
412}
413
414impl From<LiquidNetwork> for ElementsNetwork {
415 fn from(value: LiquidNetwork) -> Self {
416 match value {
417 LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
418 LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
419 LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
420 policy_asset: AssetId::from_str(
421 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
422 )
423 .unwrap(),
424 },
425 }
426 }
427}
428
429impl From<LiquidNetwork> for Chain {
430 fn from(value: LiquidNetwork) -> Self {
431 Chain::Liquid(value.into())
432 }
433}
434
435impl From<LiquidNetwork> for LiquidChain {
436 fn from(value: LiquidNetwork) -> Self {
437 match value {
438 LiquidNetwork::Mainnet => LiquidChain::Liquid,
439 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
440 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
441 }
442 }
443}
444
445impl TryFrom<&str> for LiquidNetwork {
446 type Error = anyhow::Error;
447
448 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
449 match value.to_lowercase().as_str() {
450 "mainnet" => Ok(LiquidNetwork::Mainnet),
451 "testnet" => Ok(LiquidNetwork::Testnet),
452 "regtest" => Ok(LiquidNetwork::Regtest),
453 _ => Err(anyhow!("Invalid network")),
454 }
455 }
456}
457
458impl From<LiquidNetwork> for Network {
459 fn from(value: LiquidNetwork) -> Self {
460 match value {
461 LiquidNetwork::Mainnet => Self::Bitcoin,
462 LiquidNetwork::Testnet => Self::Testnet,
463 LiquidNetwork::Regtest => Self::Regtest,
464 }
465 }
466}
467
468impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
469 fn from(value: LiquidNetwork) -> Self {
470 match value {
471 LiquidNetwork::Mainnet => Self::Bitcoin,
472 LiquidNetwork::Testnet => Self::Testnet,
473 LiquidNetwork::Regtest => Self::Regtest,
474 }
475 }
476}
477
478impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
479 fn from(value: LiquidNetwork) -> Self {
480 match value {
481 LiquidNetwork::Mainnet => Self::Bitcoin,
482 LiquidNetwork::Testnet => Self::Testnet,
483 LiquidNetwork::Regtest => Self::Regtest,
484 }
485 }
486}
487
488#[sdk_macros::async_trait]
490pub trait EventListener: Send + Sync {
491 async fn on_event(&self, e: SdkEvent);
492}
493
494#[derive(Clone, Debug, PartialEq)]
497pub enum SdkEvent {
498 PaymentFailed {
499 details: Payment,
500 },
501 PaymentPending {
502 details: Payment,
503 },
504 PaymentRefundable {
505 details: Payment,
506 },
507 PaymentRefunded {
508 details: Payment,
509 },
510 PaymentRefundPending {
511 details: Payment,
512 },
513 PaymentSucceeded {
514 details: Payment,
515 },
516 PaymentWaitingConfirmation {
517 details: Payment,
518 },
519 PaymentWaitingFeeAcceptance {
520 details: Payment,
521 },
522 Synced,
524 SyncFailed {
526 error: String,
527 },
528 DataSynced {
530 did_pull_new_records: bool,
532 },
533}
534
535#[derive(thiserror::Error, Debug)]
536pub enum SignerError {
537 #[error("Signer error: {err}")]
538 Generic { err: String },
539}
540
541impl From<anyhow::Error> for SignerError {
542 fn from(err: anyhow::Error) -> Self {
543 SignerError::Generic {
544 err: err.to_string(),
545 }
546 }
547}
548
549impl From<bip32::Error> for SignerError {
550 fn from(err: bip32::Error) -> Self {
551 SignerError::Generic {
552 err: err.to_string(),
553 }
554 }
555}
556
557pub trait Signer: Send + Sync {
560 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
563
564 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
570
571 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
573
574 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
576
577 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
579
580 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
583
584 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
586
587 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
589}
590
591pub struct ConnectRequest {
594 pub config: Config,
596 pub mnemonic: Option<String>,
598 pub passphrase: Option<String>,
600 pub seed: Option<Vec<u8>>,
602}
603
604pub struct ConnectWithSignerRequest {
605 pub config: Config,
606}
607
608#[derive(Clone, Debug)]
611pub(crate) struct ReservedAddress {
612 pub(crate) address: String,
614 pub(crate) expiry_block_height: u32,
616}
617
618#[derive(Clone, Debug, Serialize)]
620pub enum PaymentMethod {
621 Bolt11Invoice,
622 Bolt12Offer,
623 BitcoinAddress,
624 LiquidAddress,
625}
626
627#[derive(Debug, Serialize, Clone)]
628pub enum ReceiveAmount {
629 Bitcoin { payer_amount_sat: u64 },
631
632 Asset {
634 asset_id: String,
635 payer_amount: Option<f64>,
636 },
637}
638
639#[derive(Debug, Serialize)]
641pub struct PrepareReceiveRequest {
642 pub payment_method: PaymentMethod,
643 pub amount: Option<ReceiveAmount>,
645}
646
647#[derive(Debug, Serialize, Clone)]
649pub struct PrepareReceiveResponse {
650 pub payment_method: PaymentMethod,
651 pub fees_sat: u64,
660 pub amount: Option<ReceiveAmount>,
662 pub min_payer_amount_sat: Option<u64>,
666 pub max_payer_amount_sat: Option<u64>,
670 pub swapper_feerate: Option<f64>,
674}
675
676#[derive(Clone, Debug, Serialize)]
677pub enum DescriptionHash {
678 UseDescription,
679 Custom { hash: String },
680}
681
682#[derive(Debug, Serialize)]
684pub struct ReceivePaymentRequest {
685 pub prepare_response: PrepareReceiveResponse,
686 pub description: Option<String>,
688 pub description_hash: Option<DescriptionHash>,
692 pub payer_note: Option<String>,
694}
695
696#[derive(Debug, Serialize)]
698pub struct ReceivePaymentResponse {
699 pub destination: String,
702 pub liquid_expiration_blockheight: Option<u32>,
704 pub bitcoin_expiration_blockheight: Option<u32>,
706}
707
708#[derive(Debug, Serialize)]
710pub struct CreateBolt12InvoiceRequest {
711 pub offer: String,
713 pub invoice_request: String,
715}
716
717#[derive(Debug, Serialize, Clone)]
719pub struct CreateBolt12InvoiceResponse {
720 pub invoice: String,
722}
723
724#[derive(Debug, Serialize)]
726pub struct Limits {
727 pub min_sat: u64,
728 pub max_sat: u64,
729 pub max_zero_conf_sat: u64,
730}
731
732#[derive(Debug, Serialize)]
734pub struct LightningPaymentLimitsResponse {
735 pub send: Limits,
737 pub receive: Limits,
739}
740
741#[derive(Debug, Serialize)]
743pub struct OnchainPaymentLimitsResponse {
744 pub send: Limits,
746 pub receive: Limits,
748}
749
750#[derive(Debug, Serialize, Clone)]
752pub struct PrepareSendRequest {
753 pub destination: String,
756 pub amount: Option<PayAmount>,
759 pub disable_mrh: Option<bool>,
761 pub payment_timeout_sec: Option<u64>,
764}
765
766#[derive(Clone, Debug, Serialize)]
768pub enum SendDestination {
769 LiquidAddress {
770 address_data: liquid::LiquidAddressData,
771 bip353_address: Option<String>,
773 },
774 Bolt11 {
775 invoice: LNInvoice,
776 bip353_address: Option<String>,
778 },
779 Bolt12 {
780 offer: LNOffer,
781 receiver_amount_sat: u64,
782 bip353_address: Option<String>,
784 },
785}
786
787#[derive(Debug, Serialize, Clone)]
789pub struct PrepareSendResponse {
790 pub destination: SendDestination,
791 pub amount: Option<PayAmount>,
793 pub fees_sat: Option<u64>,
796 pub estimated_asset_fees: Option<f64>,
800 pub exchange_amount_sat: Option<u64>,
803 pub disable_mrh: Option<bool>,
805 pub payment_timeout_sec: Option<u64>,
807}
808
809#[derive(Debug, Serialize)]
811pub struct SendPaymentRequest {
812 pub prepare_response: PrepareSendResponse,
813 pub use_asset_fees: Option<bool>,
815 pub payer_note: Option<String>,
817}
818
819#[derive(Debug, Serialize)]
821pub struct SendPaymentResponse {
822 pub payment: Payment,
823}
824
825pub(crate) struct SendPaymentViaSwapRequest {
826 pub(crate) invoice: String,
827 pub(crate) bolt12_offer: Option<String>,
828 pub(crate) payment_hash: String,
829 pub(crate) description: Option<String>,
830 pub(crate) receiver_amount_sat: u64,
831 pub(crate) fees_sat: u64,
832}
833
834pub(crate) struct PayLiquidRequest {
835 pub address_data: LiquidAddressData,
836 pub to_asset: String,
837 pub receiver_amount_sat: u64,
838 pub asset_pay_fees: bool,
839 pub fees_sat: Option<u64>,
840}
841
842pub(crate) struct PaySideSwapRequest {
843 pub address_data: LiquidAddressData,
844 pub to_asset: String,
845 pub receiver_amount_sat: u64,
846 pub fees_sat: u64,
847 pub amount: Option<PayAmount>,
848}
849
850#[derive(Debug, Serialize, Clone)]
852pub enum PayAmount {
853 Bitcoin { receiver_amount_sat: u64 },
855
856 Asset {
858 to_asset: String,
860 receiver_amount: f64,
861 estimate_asset_fees: Option<bool>,
862 from_asset: Option<String>,
865 },
866
867 Drain,
869}
870
871impl PayAmount {
872 pub(crate) fn is_sideswap_payment(&self) -> bool {
873 match self {
874 PayAmount::Asset {
875 to_asset,
876 from_asset,
877 ..
878 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
879 _ => false,
880 }
881 }
882}
883
884#[derive(Debug, Serialize, Clone)]
886pub struct PreparePayOnchainRequest {
887 pub amount: PayAmount,
889 pub fee_rate_sat_per_vbyte: Option<u32>,
891}
892
893#[derive(Debug, Serialize, Clone)]
895pub struct PreparePayOnchainResponse {
896 pub receiver_amount_sat: u64,
897 pub claim_fees_sat: u64,
898 pub total_fees_sat: u64,
899}
900
901#[derive(Debug, Serialize)]
903pub struct PayOnchainRequest {
904 pub address: String,
905 pub prepare_response: PreparePayOnchainResponse,
906}
907
908#[derive(Debug, Serialize)]
910pub struct PrepareRefundRequest {
911 pub swap_address: String,
913 pub refund_address: String,
915 pub fee_rate_sat_per_vbyte: u32,
917}
918
919#[derive(Debug, Serialize)]
921pub struct PrepareRefundResponse {
922 pub tx_vsize: u32,
923 pub tx_fee_sat: u64,
924 pub last_refund_tx_id: Option<String>,
926}
927
928#[derive(Debug, Serialize)]
930pub struct RefundRequest {
931 pub swap_address: String,
933 pub refund_address: String,
935 pub fee_rate_sat_per_vbyte: u32,
937}
938
939#[derive(Debug, Serialize)]
941pub struct RefundResponse {
942 pub refund_tx_id: String,
943}
944
945#[derive(Clone, Debug, Default, Serialize, Deserialize)]
947pub struct AssetBalance {
948 pub asset_id: String,
949 pub balance_sat: u64,
950 pub name: Option<String>,
951 pub ticker: Option<String>,
952 pub balance: Option<f64>,
953}
954
955#[derive(Debug, Serialize, Deserialize, Default)]
956pub struct BlockchainInfo {
957 pub liquid_tip: u32,
958 pub bitcoin_tip: u32,
959}
960
961#[derive(Copy, Clone)]
962pub(crate) struct ChainTips {
963 pub liquid_tip: u32,
964 pub bitcoin_tip: Option<u32>,
965}
966
967#[derive(Debug, Serialize, Deserialize)]
968pub struct WalletInfo {
969 pub balance_sat: u64,
971 pub pending_send_sat: u64,
973 pub pending_receive_sat: u64,
975 pub fingerprint: String,
977 pub pubkey: String,
979 #[serde(default)]
981 pub asset_balances: Vec<AssetBalance>,
982}
983
984impl WalletInfo {
985 pub(crate) fn validate_sufficient_funds(
986 &self,
987 network: LiquidNetwork,
988 amount_sat: u64,
989 fees_sat: Option<u64>,
990 asset_id: &str,
991 ) -> Result<(), PaymentError> {
992 let fees_sat = fees_sat.unwrap_or(0);
993 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
994 ensure_sdk!(
995 amount_sat + fees_sat <= self.balance_sat,
996 PaymentError::InsufficientFunds
997 );
998 } else {
999 match self
1000 .asset_balances
1001 .iter()
1002 .find(|ab| ab.asset_id.eq(asset_id))
1003 {
1004 Some(asset_balance) => ensure_sdk!(
1005 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
1006 PaymentError::InsufficientFunds
1007 ),
1008 None => return Err(PaymentError::InsufficientFunds),
1009 }
1010 }
1011 Ok(())
1012 }
1013}
1014
1015#[derive(Debug, Serialize, Deserialize)]
1017pub struct GetInfoResponse {
1018 pub wallet_info: WalletInfo,
1020 #[serde(default)]
1022 pub blockchain_info: BlockchainInfo,
1023}
1024
1025#[derive(Clone, Debug, PartialEq)]
1027pub struct SignMessageRequest {
1028 pub message: String,
1029}
1030
1031#[derive(Clone, Debug, PartialEq)]
1033pub struct SignMessageResponse {
1034 pub signature: String,
1035}
1036
1037#[derive(Clone, Debug, PartialEq)]
1039pub struct CheckMessageRequest {
1040 pub message: String,
1042 pub pubkey: String,
1044 pub signature: String,
1046}
1047
1048#[derive(Clone, Debug, PartialEq)]
1050pub struct CheckMessageResponse {
1051 pub is_valid: bool,
1054}
1055
1056#[derive(Debug, Serialize)]
1058pub struct BackupRequest {
1059 pub backup_path: Option<String>,
1066}
1067
1068#[derive(Debug, Serialize)]
1070pub struct RestoreRequest {
1071 pub backup_path: Option<String>,
1072}
1073
1074#[derive(Default)]
1076pub struct ListPaymentsRequest {
1077 pub filters: Option<Vec<PaymentType>>,
1078 pub states: Option<Vec<PaymentState>>,
1079 pub from_timestamp: Option<i64>,
1081 pub to_timestamp: Option<i64>,
1083 pub offset: Option<u32>,
1084 pub limit: Option<u32>,
1085 pub details: Option<ListPaymentDetails>,
1086 pub sort_ascending: Option<bool>,
1087}
1088
1089#[derive(Debug, Serialize)]
1091pub enum ListPaymentDetails {
1092 Liquid {
1094 asset_id: Option<String>,
1096 destination: Option<String>,
1098 },
1099
1100 Bitcoin {
1102 address: Option<String>,
1104 },
1105}
1106
1107#[derive(Debug, Serialize)]
1109pub enum GetPaymentRequest {
1110 PaymentHash { payment_hash: String },
1112 SwapId { swap_id: String },
1114}
1115
1116#[sdk_macros::async_trait]
1118pub(crate) trait BlockListener: Send + Sync {
1119 async fn on_bitcoin_block(&self, height: u32);
1120 async fn on_liquid_block(&self, height: u32);
1121}
1122
1123#[derive(Clone, Debug)]
1125pub enum Swap {
1126 Chain(ChainSwap),
1127 Send(SendSwap),
1128 Receive(ReceiveSwap),
1129}
1130impl Swap {
1131 pub(crate) fn id(&self) -> String {
1132 match &self {
1133 Swap::Chain(ChainSwap { id, .. })
1134 | Swap::Send(SendSwap { id, .. })
1135 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1136 }
1137 }
1138
1139 pub(crate) fn version(&self) -> u64 {
1140 match self {
1141 Swap::Chain(ChainSwap { metadata, .. })
1142 | Swap::Send(SendSwap { metadata, .. })
1143 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1144 }
1145 }
1146
1147 pub(crate) fn set_version(&mut self, version: u64) {
1148 match self {
1149 Swap::Chain(chain_swap) => {
1150 chain_swap.metadata.version = version;
1151 }
1152 Swap::Send(send_swap) => {
1153 send_swap.metadata.version = version;
1154 }
1155 Swap::Receive(receive_swap) => {
1156 receive_swap.metadata.version = version;
1157 }
1158 }
1159 }
1160
1161 pub(crate) fn last_updated_at(&self) -> u32 {
1162 match self {
1163 Swap::Chain(ChainSwap { metadata, .. })
1164 | Swap::Send(SendSwap { metadata, .. })
1165 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1166 }
1167 }
1168}
1169impl From<ChainSwap> for Swap {
1170 fn from(swap: ChainSwap) -> Self {
1171 Self::Chain(swap)
1172 }
1173}
1174impl From<SendSwap> for Swap {
1175 fn from(swap: SendSwap) -> Self {
1176 Self::Send(swap)
1177 }
1178}
1179impl From<ReceiveSwap> for Swap {
1180 fn from(swap: ReceiveSwap) -> Self {
1181 Self::Receive(swap)
1182 }
1183}
1184
1185#[derive(Clone, Debug)]
1186pub(crate) enum SwapScriptV2 {
1187 Bitcoin(BtcSwapScript),
1188 Liquid(LBtcSwapScript),
1189}
1190impl SwapScriptV2 {
1191 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1192 match self {
1193 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1194 _ => Err(anyhow!("Invalid chain")),
1195 }
1196 }
1197
1198 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1199 match self {
1200 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1201 _ => Err(anyhow!("Invalid chain")),
1202 }
1203 }
1204}
1205
1206#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1207pub enum Direction {
1208 Incoming = 0,
1209 Outgoing = 1,
1210}
1211impl ToSql for Direction {
1212 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1213 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1214 }
1215}
1216impl FromSql for Direction {
1217 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1218 match value {
1219 ValueRef::Integer(i) => match i as u8 {
1220 0 => Ok(Direction::Incoming),
1221 1 => Ok(Direction::Outgoing),
1222 _ => Err(FromSqlError::OutOfRange(i)),
1223 },
1224 _ => Err(FromSqlError::InvalidType),
1225 }
1226 }
1227}
1228
1229#[derive(Clone, Debug, Default)]
1230pub(crate) struct SwapMetadata {
1231 pub(crate) version: u64,
1233 pub(crate) last_updated_at: u32,
1234 pub(crate) is_local: bool,
1235}
1236
1237#[derive(Clone, Debug, Derivative)]
1241#[derivative(PartialEq)]
1242pub struct ChainSwap {
1243 pub(crate) id: String,
1244 pub(crate) direction: Direction,
1245 pub(crate) claim_address: Option<String>,
1248 pub(crate) lockup_address: String,
1249 pub(crate) refund_address: Option<String>,
1251 pub(crate) timeout_block_height: u32,
1253 pub(crate) claim_timeout_block_height: u32,
1255 pub(crate) preimage: String,
1256 pub(crate) description: Option<String>,
1257 pub(crate) payer_amount_sat: u64,
1259 pub(crate) actual_payer_amount_sat: Option<u64>,
1262 pub(crate) receiver_amount_sat: u64,
1264 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1266 pub(crate) claim_fees_sat: u64,
1267 pub(crate) pair_fees_json: String,
1269 pub(crate) accept_zero_conf: bool,
1270 pub(crate) create_response_json: String,
1272 pub(crate) server_lockup_tx_id: Option<String>,
1274 pub(crate) user_lockup_tx_id: Option<String>,
1276 pub(crate) claim_tx_id: Option<String>,
1278 pub(crate) refund_tx_id: Option<String>,
1280 pub(crate) created_at: u32,
1281 pub(crate) state: PaymentState,
1282 pub(crate) claim_private_key: String,
1283 pub(crate) refund_private_key: String,
1284 pub(crate) auto_accepted_fees: bool,
1285 #[derivative(PartialEq = "ignore")]
1287 pub(crate) metadata: SwapMetadata,
1288}
1289impl ChainSwap {
1290 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1291 utils::decode_keypair(&self.claim_private_key)
1292 }
1293
1294 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1295 utils::decode_keypair(&self.refund_private_key)
1296 }
1297
1298 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1299 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1300 serde_json::from_str(&self.create_response_json).map_err(|e| {
1301 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1302 })?;
1303
1304 Ok(CreateChainResponse {
1305 id: self.id.clone(),
1306 claim_details: internal_create_response.claim_details,
1307 lockup_details: internal_create_response.lockup_details,
1308 })
1309 }
1310
1311 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1312 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1313 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1314
1315 Ok(pair)
1316 }
1317
1318 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1319 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1320 let our_pubkey = self.get_claim_keypair()?.public_key();
1321 let swap_script = match self.direction {
1322 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1323 Side::Claim,
1324 chain_swap_details,
1325 our_pubkey.into(),
1326 )?),
1327 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1328 Side::Claim,
1329 chain_swap_details,
1330 our_pubkey.into(),
1331 )?),
1332 };
1333 Ok(swap_script)
1334 }
1335
1336 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1337 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1338 let our_pubkey = self.get_refund_keypair()?.public_key();
1339 let swap_script = match self.direction {
1340 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1341 Side::Lockup,
1342 chain_swap_details,
1343 our_pubkey.into(),
1344 )?),
1345 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1346 Side::Lockup,
1347 chain_swap_details,
1348 our_pubkey.into(),
1349 )?),
1350 };
1351 Ok(swap_script)
1352 }
1353
1354 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1356 &self,
1357 network: LiquidNetwork,
1358 ) -> SdkResult<ScriptBuf> {
1359 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1360 let script_pubkey = swap_script
1361 .to_address(network.as_bitcoin_chain())
1362 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1363 .script_pubkey();
1364 Ok(script_pubkey)
1365 }
1366
1367 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1368 RefundableSwap {
1369 swap_address: self.lockup_address.clone(),
1370 timestamp: self.created_at,
1371 amount_sat,
1372 last_refund_tx_id: self.refund_tx_id.clone(),
1373 }
1374 }
1375
1376 pub(crate) fn from_boltz_struct_to_json(
1377 create_response: &CreateChainResponse,
1378 expected_swap_id: &str,
1379 ) -> Result<String, PaymentError> {
1380 let internal_create_response =
1381 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1382 create_response,
1383 expected_swap_id,
1384 )?;
1385
1386 let create_response_json =
1387 serde_json::to_string(&internal_create_response).map_err(|e| {
1388 PaymentError::Generic {
1389 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1390 }
1391 })?;
1392
1393 Ok(create_response_json)
1394 }
1395
1396 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1397 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1398 }
1399}
1400
1401#[derive(Clone, Debug, Default)]
1402pub(crate) struct ChainSwapUpdate {
1403 pub(crate) swap_id: String,
1404 pub(crate) to_state: PaymentState,
1405 pub(crate) server_lockup_tx_id: Option<String>,
1406 pub(crate) user_lockup_tx_id: Option<String>,
1407 pub(crate) claim_address: Option<String>,
1408 pub(crate) claim_tx_id: Option<String>,
1409 pub(crate) refund_tx_id: Option<String>,
1410}
1411
1412#[derive(Clone, Debug, Derivative)]
1414#[derivative(PartialEq)]
1415pub struct SendSwap {
1416 pub(crate) id: String,
1417 pub(crate) invoice: String,
1419 pub(crate) bolt12_offer: Option<String>,
1421 pub(crate) payment_hash: Option<String>,
1422 pub(crate) destination_pubkey: Option<String>,
1423 pub(crate) description: Option<String>,
1424 pub(crate) preimage: Option<String>,
1425 pub(crate) payer_amount_sat: u64,
1426 pub(crate) receiver_amount_sat: u64,
1427 pub(crate) pair_fees_json: String,
1429 pub(crate) create_response_json: String,
1431 pub(crate) lockup_tx_id: Option<String>,
1433 pub(crate) refund_address: Option<String>,
1435 pub(crate) refund_tx_id: Option<String>,
1437 pub(crate) created_at: u32,
1438 pub(crate) timeout_block_height: u64,
1439 pub(crate) state: PaymentState,
1440 pub(crate) refund_private_key: String,
1441 #[derivative(PartialEq = "ignore")]
1443 pub(crate) metadata: SwapMetadata,
1444}
1445impl SendSwap {
1446 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1447 utils::decode_keypair(&self.refund_private_key)
1448 }
1449
1450 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1451 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1452 serde_json::from_str(&self.create_response_json).map_err(|e| {
1453 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1454 })?;
1455
1456 let res = CreateSubmarineResponse {
1457 id: self.id.clone(),
1458 accept_zero_conf: internal_create_response.accept_zero_conf,
1459 address: internal_create_response.address.clone(),
1460 bip21: internal_create_response.bip21.clone(),
1461 claim_public_key: crate::utils::json_to_pubkey(
1462 &internal_create_response.claim_public_key,
1463 )?,
1464 expected_amount: internal_create_response.expected_amount,
1465 referral_id: internal_create_response.referral_id,
1466 swap_tree: internal_create_response.swap_tree.clone().into(),
1467 timeout_block_height: internal_create_response.timeout_block_height,
1468 blinding_key: internal_create_response.blinding_key.clone(),
1469 };
1470 Ok(res)
1471 }
1472
1473 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1474 LBtcSwapScript::submarine_from_swap_resp(
1475 &self.get_boltz_create_response()?,
1476 self.get_refund_keypair()?.public_key().into(),
1477 )
1478 .map_err(|e| {
1479 SdkError::generic(format!(
1480 "Failed to create swap script for Send Swap {}: {e:?}",
1481 self.id
1482 ))
1483 })
1484 }
1485
1486 pub(crate) fn from_boltz_struct_to_json(
1487 create_response: &CreateSubmarineResponse,
1488 expected_swap_id: &str,
1489 ) -> Result<String, PaymentError> {
1490 let internal_create_response =
1491 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1492 create_response,
1493 expected_swap_id,
1494 )?;
1495
1496 let create_response_json =
1497 serde_json::to_string(&internal_create_response).map_err(|e| {
1498 PaymentError::Generic {
1499 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1500 }
1501 })?;
1502
1503 Ok(create_response_json)
1504 }
1505}
1506
1507#[derive(Clone, Debug, Derivative)]
1509#[derivative(PartialEq)]
1510pub struct ReceiveSwap {
1511 pub(crate) id: String,
1512 pub(crate) preimage: String,
1513 pub(crate) create_response_json: String,
1515 pub(crate) claim_private_key: String,
1516 pub(crate) invoice: String,
1517 pub(crate) bolt12_offer: Option<String>,
1519 pub(crate) payment_hash: Option<String>,
1520 pub(crate) destination_pubkey: Option<String>,
1521 pub(crate) description: Option<String>,
1522 pub(crate) payer_note: Option<String>,
1523 pub(crate) payer_amount_sat: u64,
1525 pub(crate) receiver_amount_sat: u64,
1526 pub(crate) pair_fees_json: String,
1528 pub(crate) claim_fees_sat: u64,
1529 pub(crate) claim_address: Option<String>,
1531 pub(crate) claim_tx_id: Option<String>,
1533 pub(crate) lockup_tx_id: Option<String>,
1535 pub(crate) mrh_address: String,
1537 pub(crate) mrh_tx_id: Option<String>,
1539 pub(crate) created_at: u32,
1542 pub(crate) timeout_block_height: u32,
1543 pub(crate) state: PaymentState,
1544 #[derivative(PartialEq = "ignore")]
1546 pub(crate) metadata: SwapMetadata,
1547}
1548impl ReceiveSwap {
1549 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1550 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1551 }
1552
1553 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1554 Ok(self
1555 .get_swap_script()?
1556 .funding_addrs
1557 .ok_or(anyhow!("No funding address found"))?
1558 .script_pubkey())
1559 }
1560
1561 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1562 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1563 serde_json::from_str(&self.create_response_json).map_err(|e| {
1564 PaymentError::Generic {
1565 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1566 }
1567 })?;
1568
1569 let res = CreateReverseResponse {
1570 id: self.id.clone(),
1571 invoice: Some(self.invoice.clone()),
1572 swap_tree: internal_create_response.swap_tree.clone().into(),
1573 lockup_address: internal_create_response.lockup_address.clone(),
1574 refund_public_key: crate::utils::json_to_pubkey(
1575 &internal_create_response.refund_public_key,
1576 )?,
1577 timeout_block_height: internal_create_response.timeout_block_height,
1578 onchain_amount: internal_create_response.onchain_amount,
1579 blinding_key: internal_create_response.blinding_key.clone(),
1580 };
1581 Ok(res)
1582 }
1583
1584 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1585 let keypair = self.get_claim_keypair()?;
1586 let create_response =
1587 self.get_boltz_create_response()
1588 .map_err(|e| PaymentError::Generic {
1589 err: format!(
1590 "Failed to create swap script for Receive Swap {}: {e:?}",
1591 self.id
1592 ),
1593 })?;
1594 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1595 .map_err(|e| PaymentError::Generic {
1596 err: format!(
1597 "Failed to create swap script for Receive Swap {}: {e:?}",
1598 self.id
1599 ),
1600 })
1601 }
1602
1603 pub(crate) fn from_boltz_struct_to_json(
1604 create_response: &CreateReverseResponse,
1605 expected_swap_id: &str,
1606 expected_invoice: Option<&str>,
1607 ) -> Result<String, PaymentError> {
1608 let internal_create_response =
1609 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1610 create_response,
1611 expected_swap_id,
1612 expected_invoice,
1613 )?;
1614
1615 let create_response_json =
1616 serde_json::to_string(&internal_create_response).map_err(|e| {
1617 PaymentError::Generic {
1618 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1619 }
1620 })?;
1621
1622 Ok(create_response_json)
1623 }
1624}
1625
1626#[derive(Clone, Debug, PartialEq, Serialize)]
1628pub struct RefundableSwap {
1629 pub swap_address: String,
1630 pub timestamp: u32,
1631 pub amount_sat: u64,
1633 pub last_refund_tx_id: Option<String>,
1635}
1636
1637#[derive(Clone, Debug, Derivative)]
1639#[derivative(PartialEq)]
1640pub(crate) struct Bolt12Offer {
1641 pub(crate) id: String,
1643 pub(crate) description: String,
1645 pub(crate) private_key: String,
1647 pub(crate) webhook_url: Option<String>,
1649 pub(crate) created_at: u32,
1651}
1652impl Bolt12Offer {
1653 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1654 utils::decode_keypair(&self.private_key)
1655 }
1656}
1657impl TryFrom<Bolt12Offer> for Offer {
1658 type Error = SdkError;
1659
1660 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1661 Offer::from_str(&val.id)
1662 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1663 }
1664}
1665
1666#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1668#[strum(serialize_all = "lowercase")]
1669pub enum PaymentState {
1670 #[default]
1671 Created = 0,
1672
1673 Pending = 1,
1693
1694 Complete = 2,
1706
1707 Failed = 3,
1715
1716 TimedOut = 4,
1721
1722 Refundable = 5,
1727
1728 RefundPending = 6,
1734
1735 WaitingFeeAcceptance = 7,
1747}
1748
1749impl ToSql for PaymentState {
1750 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1751 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1752 }
1753}
1754impl FromSql for PaymentState {
1755 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1756 match value {
1757 ValueRef::Integer(i) => match i as u8 {
1758 0 => Ok(PaymentState::Created),
1759 1 => Ok(PaymentState::Pending),
1760 2 => Ok(PaymentState::Complete),
1761 3 => Ok(PaymentState::Failed),
1762 4 => Ok(PaymentState::TimedOut),
1763 5 => Ok(PaymentState::Refundable),
1764 6 => Ok(PaymentState::RefundPending),
1765 7 => Ok(PaymentState::WaitingFeeAcceptance),
1766 _ => Err(FromSqlError::OutOfRange(i)),
1767 },
1768 _ => Err(FromSqlError::InvalidType),
1769 }
1770 }
1771}
1772
1773impl PaymentState {
1774 pub(crate) fn is_refundable(&self) -> bool {
1775 matches!(
1776 self,
1777 PaymentState::Refundable
1778 | PaymentState::RefundPending
1779 | PaymentState::WaitingFeeAcceptance
1780 )
1781 }
1782}
1783
1784#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1785#[strum(serialize_all = "lowercase")]
1786pub enum PaymentType {
1787 Receive = 0,
1788 Send = 1,
1789}
1790impl From<Direction> for PaymentType {
1791 fn from(value: Direction) -> Self {
1792 match value {
1793 Direction::Incoming => Self::Receive,
1794 Direction::Outgoing => Self::Send,
1795 }
1796 }
1797}
1798impl ToSql for PaymentType {
1799 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1800 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1801 }
1802}
1803impl FromSql for PaymentType {
1804 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1805 match value {
1806 ValueRef::Integer(i) => match i as u8 {
1807 0 => Ok(PaymentType::Receive),
1808 1 => Ok(PaymentType::Send),
1809 _ => Err(FromSqlError::OutOfRange(i)),
1810 },
1811 _ => Err(FromSqlError::InvalidType),
1812 }
1813 }
1814}
1815
1816#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1817pub enum PaymentStatus {
1818 Pending = 0,
1819 Complete = 1,
1820}
1821impl ToSql for PaymentStatus {
1822 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1823 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1824 }
1825}
1826impl FromSql for PaymentStatus {
1827 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1828 match value {
1829 ValueRef::Integer(i) => match i as u8 {
1830 0 => Ok(PaymentStatus::Pending),
1831 1 => Ok(PaymentStatus::Complete),
1832 _ => Err(FromSqlError::OutOfRange(i)),
1833 },
1834 _ => Err(FromSqlError::InvalidType),
1835 }
1836 }
1837}
1838
1839#[derive(Debug, Clone, Serialize)]
1840pub struct PaymentTxData {
1841 pub tx_id: String,
1843
1844 pub timestamp: Option<u32>,
1846
1847 pub fees_sat: u64,
1849
1850 pub is_confirmed: bool,
1852
1853 pub unblinding_data: Option<String>,
1856}
1857
1858#[derive(Debug, Clone, Serialize)]
1859pub enum PaymentSwapType {
1860 Receive,
1861 Send,
1862 Chain,
1863}
1864
1865#[derive(Debug, Clone, Serialize)]
1866pub struct PaymentSwapData {
1867 pub swap_id: String,
1868
1869 pub swap_type: PaymentSwapType,
1870
1871 pub created_at: u32,
1873
1874 pub expiration_blockheight: u32,
1877
1878 pub claim_expiration_blockheight: Option<u32>,
1880
1881 pub preimage: Option<String>,
1882 pub invoice: Option<String>,
1883 pub bolt12_offer: Option<String>,
1884 pub payment_hash: Option<String>,
1885 pub destination_pubkey: Option<String>,
1886 pub description: String,
1887 pub payer_note: Option<String>,
1888
1889 pub payer_amount_sat: u64,
1891
1892 pub receiver_amount_sat: u64,
1894
1895 pub swapper_fees_sat: u64,
1897
1898 pub refund_tx_id: Option<String>,
1899 pub refund_tx_amount_sat: Option<u64>,
1900
1901 pub bitcoin_address: Option<String>,
1904
1905 pub status: PaymentState,
1907}
1908
1909#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1911pub struct LnUrlInfo {
1912 pub ln_address: Option<String>,
1913 pub lnurl_pay_comment: Option<String>,
1914 pub lnurl_pay_domain: Option<String>,
1915 pub lnurl_pay_metadata: Option<String>,
1916 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1917 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1918 pub lnurl_withdraw_endpoint: Option<String>,
1919}
1920
1921#[derive(Debug, Clone, Serialize)]
1925pub struct AssetMetadata {
1926 pub asset_id: String,
1928 pub name: String,
1930 pub ticker: String,
1932 pub precision: u8,
1935 pub fiat_id: Option<String>,
1937}
1938
1939impl AssetMetadata {
1940 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1941 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1942 }
1943
1944 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1945 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1946 }
1947}
1948
1949#[derive(Clone, Debug, PartialEq, Serialize)]
1952pub struct AssetInfo {
1953 pub name: String,
1955 pub ticker: String,
1957 pub amount: f64,
1960 pub fees: Option<f64>,
1963}
1964
1965#[derive(Debug, Clone, PartialEq, Serialize)]
1967#[allow(clippy::large_enum_variant)]
1968pub enum PaymentDetails {
1969 Lightning {
1971 swap_id: String,
1972
1973 description: String,
1975
1976 liquid_expiration_blockheight: u32,
1978
1979 preimage: Option<String>,
1981
1982 invoice: Option<String>,
1986
1987 bolt12_offer: Option<String>,
1988
1989 payment_hash: Option<String>,
1991
1992 destination_pubkey: Option<String>,
1994
1995 lnurl_info: Option<LnUrlInfo>,
1997
1998 bip353_address: Option<String>,
2000
2001 payer_note: Option<String>,
2003
2004 claim_tx_id: Option<String>,
2006
2007 refund_tx_id: Option<String>,
2009
2010 refund_tx_amount_sat: Option<u64>,
2012 },
2013 Liquid {
2015 destination: String,
2017
2018 description: String,
2020
2021 asset_id: String,
2023
2024 asset_info: Option<AssetInfo>,
2026
2027 lnurl_info: Option<LnUrlInfo>,
2029
2030 bip353_address: Option<String>,
2032
2033 payer_note: Option<String>,
2035 },
2036 Bitcoin {
2038 swap_id: String,
2039
2040 bitcoin_address: String,
2042
2043 description: String,
2045
2046 auto_accepted_fees: bool,
2050
2051 liquid_expiration_blockheight: u32,
2053
2054 bitcoin_expiration_blockheight: u32,
2056
2057 lockup_tx_id: Option<String>,
2059
2060 claim_tx_id: Option<String>,
2062
2063 refund_tx_id: Option<String>,
2065
2066 refund_tx_amount_sat: Option<u64>,
2068 },
2069}
2070
2071impl PaymentDetails {
2072 pub(crate) fn get_swap_id(&self) -> Option<String> {
2073 match self {
2074 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2075 Some(swap_id.clone())
2076 }
2077 Self::Liquid { .. } => None,
2078 }
2079 }
2080
2081 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2082 match self {
2083 Self::Lightning {
2084 refund_tx_amount_sat,
2085 ..
2086 }
2087 | Self::Bitcoin {
2088 refund_tx_amount_sat,
2089 ..
2090 } => *refund_tx_amount_sat,
2091 Self::Liquid { .. } => None,
2092 }
2093 }
2094
2095 pub(crate) fn get_description(&self) -> Option<String> {
2096 match self {
2097 Self::Lightning { description, .. }
2098 | Self::Bitcoin { description, .. }
2099 | Self::Liquid { description, .. } => Some(description.clone()),
2100 }
2101 }
2102
2103 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2104 match self {
2105 Self::Liquid { asset_id, .. } => {
2106 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2107 }
2108 _ => true,
2109 }
2110 }
2111}
2112
2113#[derive(Debug, Clone, PartialEq, Serialize)]
2117pub struct Payment {
2118 pub destination: Option<String>,
2121
2122 pub tx_id: Option<String>,
2123
2124 pub unblinding_data: Option<String>,
2127
2128 pub timestamp: u32,
2134
2135 pub amount_sat: u64,
2139
2140 pub fees_sat: u64,
2154
2155 pub swapper_fees_sat: Option<u64>,
2158
2159 pub payment_type: PaymentType,
2161
2162 pub status: PaymentState,
2168
2169 pub details: PaymentDetails,
2172}
2173impl Payment {
2174 pub(crate) fn from_pending_swap(
2175 swap: PaymentSwapData,
2176 payment_type: PaymentType,
2177 payment_details: PaymentDetails,
2178 ) -> Payment {
2179 let amount_sat = match payment_type {
2180 PaymentType::Receive => swap.receiver_amount_sat,
2181 PaymentType::Send => swap.payer_amount_sat,
2182 };
2183
2184 Payment {
2185 destination: swap.invoice.clone(),
2186 tx_id: None,
2187 unblinding_data: None,
2188 timestamp: swap.created_at,
2189 amount_sat,
2190 fees_sat: swap
2191 .payer_amount_sat
2192 .saturating_sub(swap.receiver_amount_sat),
2193 swapper_fees_sat: Some(swap.swapper_fees_sat),
2194 payment_type,
2195 status: swap.status,
2196 details: payment_details,
2197 }
2198 }
2199
2200 pub(crate) fn from_tx_data(
2201 tx: PaymentTxData,
2202 balance: PaymentTxBalance,
2203 swap: Option<PaymentSwapData>,
2204 details: PaymentDetails,
2205 ) -> Payment {
2206 let (amount_sat, fees_sat) = match swap.as_ref() {
2207 Some(s) => match balance.payment_type {
2208 PaymentType::Receive => (
2212 balance.amount,
2213 s.payer_amount_sat.saturating_sub(balance.amount),
2214 ),
2215 PaymentType::Send => (
2216 s.receiver_amount_sat,
2217 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2218 ),
2219 },
2220 None => {
2221 let (amount_sat, fees_sat) = match balance.payment_type {
2222 PaymentType::Receive => (balance.amount, 0),
2223 PaymentType::Send => (balance.amount, tx.fees_sat),
2224 };
2225 match details {
2228 PaymentDetails::Liquid {
2229 asset_info: Some(ref asset_info),
2230 ..
2231 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2232 _ => (amount_sat, fees_sat),
2233 }
2234 }
2235 };
2236 Payment {
2237 tx_id: Some(tx.tx_id),
2238 unblinding_data: tx.unblinding_data,
2239 destination: match &swap {
2243 Some(PaymentSwapData {
2244 swap_type: PaymentSwapType::Receive,
2245 invoice,
2246 ..
2247 }) => invoice.clone(),
2248 Some(PaymentSwapData {
2249 swap_type: PaymentSwapType::Send,
2250 invoice,
2251 bolt12_offer,
2252 ..
2253 }) => bolt12_offer.clone().or(invoice.clone()),
2254 Some(PaymentSwapData {
2255 swap_type: PaymentSwapType::Chain,
2256 bitcoin_address,
2257 ..
2258 }) => bitcoin_address.clone(),
2259 _ => match &details {
2260 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2261 _ => None,
2262 },
2263 },
2264 timestamp: tx
2265 .timestamp
2266 .or(swap.as_ref().map(|s| s.created_at))
2267 .unwrap_or(utils::now()),
2268 amount_sat,
2269 fees_sat,
2270 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2271 payment_type: balance.payment_type,
2272 status: match &swap {
2273 Some(swap) => swap.status,
2274 None => match tx.is_confirmed {
2275 true => PaymentState::Complete,
2276 false => PaymentState::Pending,
2277 },
2278 },
2279 details,
2280 }
2281 }
2282
2283 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2284 match self.details.clone() {
2285 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2286 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2287 PaymentDetails::Liquid { .. } => None,
2288 }
2289 .flatten()
2290 }
2291}
2292
2293#[derive(Deserialize, Serialize, Clone, Debug)]
2295#[serde(rename_all = "camelCase")]
2296pub struct RecommendedFees {
2297 pub fastest_fee: u64,
2298 pub half_hour_fee: u64,
2299 pub hour_fee: u64,
2300 pub economy_fee: u64,
2301 pub minimum_fee: u64,
2302}
2303
2304#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2306pub enum BuyBitcoinProvider {
2307 #[strum(serialize = "moonpay")]
2308 Moonpay,
2309}
2310
2311#[derive(Debug, Serialize)]
2313pub struct PrepareBuyBitcoinRequest {
2314 pub provider: BuyBitcoinProvider,
2315 pub amount_sat: u64,
2316}
2317
2318#[derive(Clone, Debug, Serialize)]
2320pub struct PrepareBuyBitcoinResponse {
2321 pub provider: BuyBitcoinProvider,
2322 pub amount_sat: u64,
2323 pub fees_sat: u64,
2324}
2325
2326#[derive(Clone, Debug, Serialize)]
2328pub struct BuyBitcoinRequest {
2329 pub prepare_response: PrepareBuyBitcoinResponse,
2330 pub redirect_url: Option<String>,
2334}
2335
2336#[derive(Clone, Debug)]
2338pub struct LogEntry {
2339 pub line: String,
2340 pub level: String,
2341}
2342
2343#[derive(Clone, Debug, Serialize, Deserialize)]
2344struct InternalLeaf {
2345 pub output: String,
2346 pub version: u8,
2347}
2348impl From<InternalLeaf> for Leaf {
2349 fn from(value: InternalLeaf) -> Self {
2350 Leaf {
2351 output: value.output,
2352 version: value.version,
2353 }
2354 }
2355}
2356impl From<Leaf> for InternalLeaf {
2357 fn from(value: Leaf) -> Self {
2358 InternalLeaf {
2359 output: value.output,
2360 version: value.version,
2361 }
2362 }
2363}
2364
2365#[derive(Clone, Debug, Serialize, Deserialize)]
2366pub(super) struct InternalSwapTree {
2367 claim_leaf: InternalLeaf,
2368 refund_leaf: InternalLeaf,
2369}
2370impl From<InternalSwapTree> for SwapTree {
2371 fn from(value: InternalSwapTree) -> Self {
2372 SwapTree {
2373 claim_leaf: value.claim_leaf.into(),
2374 refund_leaf: value.refund_leaf.into(),
2375 }
2376 }
2377}
2378impl From<SwapTree> for InternalSwapTree {
2379 fn from(value: SwapTree) -> Self {
2380 InternalSwapTree {
2381 claim_leaf: value.claim_leaf.into(),
2382 refund_leaf: value.refund_leaf.into(),
2383 }
2384 }
2385}
2386
2387#[derive(Debug, Serialize)]
2389pub struct PrepareLnUrlPayRequest {
2390 pub data: LnUrlPayRequestData,
2392 pub amount: PayAmount,
2394 pub bip353_address: Option<String>,
2397 pub comment: Option<String>,
2400 pub validate_success_action_url: Option<bool>,
2403}
2404
2405#[derive(Debug, Serialize)]
2407pub struct PrepareLnUrlPayResponse {
2408 pub destination: SendDestination,
2410 pub fees_sat: u64,
2412 pub data: LnUrlPayRequestData,
2414 pub amount: PayAmount,
2416 pub comment: Option<String>,
2419 pub success_action: Option<SuccessAction>,
2422}
2423
2424#[derive(Debug, Serialize)]
2426pub struct LnUrlPayRequest {
2427 pub prepare_response: PrepareLnUrlPayResponse,
2429}
2430
2431#[derive(Serialize)]
2443#[allow(clippy::large_enum_variant)]
2444pub enum LnUrlPayResult {
2445 EndpointSuccess { data: LnUrlPaySuccessData },
2446 EndpointError { data: LnUrlErrorData },
2447 PayError { data: LnUrlPayErrorData },
2448}
2449
2450#[derive(Serialize)]
2451pub struct LnUrlPaySuccessData {
2452 pub payment: Payment,
2453 pub success_action: Option<SuccessActionProcessed>,
2454}
2455
2456#[derive(Debug, Clone)]
2457pub enum Transaction {
2458 Liquid(boltz_client::elements::Transaction),
2459 Bitcoin(boltz_client::bitcoin::Transaction),
2460}
2461
2462impl Transaction {
2463 pub(crate) fn txid(&self) -> String {
2464 match self {
2465 Transaction::Liquid(tx) => tx.txid().to_hex(),
2466 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2467 }
2468 }
2469}
2470
2471#[derive(Debug, Clone)]
2472pub enum Utxo {
2473 Liquid(
2474 Box<(
2475 boltz_client::elements::OutPoint,
2476 boltz_client::elements::TxOut,
2477 )>,
2478 ),
2479 Bitcoin(
2480 (
2481 boltz_client::bitcoin::OutPoint,
2482 boltz_client::bitcoin::TxOut,
2483 ),
2484 ),
2485}
2486
2487impl Utxo {
2488 pub(crate) fn as_bitcoin(
2489 &self,
2490 ) -> Option<&(
2491 boltz_client::bitcoin::OutPoint,
2492 boltz_client::bitcoin::TxOut,
2493 )> {
2494 match self {
2495 Utxo::Liquid(_) => None,
2496 Utxo::Bitcoin(utxo) => Some(utxo),
2497 }
2498 }
2499
2500 pub(crate) fn as_liquid(
2501 &self,
2502 ) -> Option<
2503 Box<(
2504 boltz_client::elements::OutPoint,
2505 boltz_client::elements::TxOut,
2506 )>,
2507 > {
2508 match self {
2509 Utxo::Bitcoin(_) => None,
2510 Utxo::Liquid(utxo) => Some(utxo.clone()),
2511 }
2512 }
2513}
2514
2515#[derive(Debug, Clone)]
2517pub struct FetchPaymentProposedFeesRequest {
2518 pub swap_id: String,
2519}
2520
2521#[derive(Debug, Clone, Serialize)]
2523pub struct FetchPaymentProposedFeesResponse {
2524 pub swap_id: String,
2525 pub fees_sat: u64,
2526 pub payer_amount_sat: u64,
2528 pub receiver_amount_sat: u64,
2530}
2531
2532#[derive(Debug, Clone)]
2534pub struct AcceptPaymentProposedFeesRequest {
2535 pub response: FetchPaymentProposedFeesResponse,
2536}
2537
2538#[derive(Clone, Debug)]
2539pub struct History<T> {
2540 pub txid: T,
2541 pub height: i32,
2546}
2547pub(crate) type LBtcHistory = History<elements::Txid>;
2548pub(crate) type BtcHistory = History<bitcoin::Txid>;
2549
2550impl<T> History<T> {
2551 pub(crate) fn confirmed(&self) -> bool {
2552 self.height > 0
2553 }
2554}
2555#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2556impl From<electrum_client::GetHistoryRes> for BtcHistory {
2557 fn from(value: electrum_client::GetHistoryRes) -> Self {
2558 Self {
2559 txid: value.tx_hash,
2560 height: value.height,
2561 }
2562 }
2563}
2564impl From<lwk_wollet::History> for LBtcHistory {
2565 fn from(value: lwk_wollet::History) -> Self {
2566 Self::from(&value)
2567 }
2568}
2569impl From<&lwk_wollet::History> for LBtcHistory {
2570 fn from(value: &lwk_wollet::History) -> Self {
2571 Self {
2572 txid: value.txid,
2573 height: value.height,
2574 }
2575 }
2576}
2577pub(crate) type BtcScript = bitcoin::ScriptBuf;
2578pub(crate) type LBtcScript = elements::Script;
2579
2580#[derive(Clone, Debug)]
2581pub struct BtcScriptBalance {
2582 pub confirmed: u64,
2584 pub unconfirmed: i64,
2588}
2589#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2590impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2591 fn from(val: electrum_client::GetBalanceRes) -> Self {
2592 Self {
2593 confirmed: val.confirmed,
2594 unconfirmed: val.unconfirmed,
2595 }
2596 }
2597}
2598
2599pub(crate) struct GetSyncContextRequest {
2600 pub partial_sync: Option<bool>,
2601 pub last_liquid_tip: u32,
2602 pub last_bitcoin_tip: u32,
2603}
2604
2605pub(crate) struct SyncContext {
2606 pub maybe_liquid_tip: Option<u32>,
2607 pub maybe_bitcoin_tip: Option<u32>,
2608 pub recoverable_swaps: Vec<Swap>,
2609 pub is_new_liquid_block: bool,
2610 pub is_new_bitcoin_block: bool,
2611}
2612
2613pub(crate) struct TaskHandle {
2614 pub name: String,
2615 pub handle: tokio::task::JoinHandle<()>,
2616}
2617
2618#[macro_export]
2619macro_rules! get_updated_fields {
2620 ($($var:ident),* $(,)?) => {{
2621 let mut options = Vec::new();
2622 $(
2623 if $var.is_some() {
2624 options.push(stringify!($var).to_string());
2625 }
2626 )*
2627 match options.len() > 0 {
2628 true => Some(options),
2629 false => None,
2630 }
2631 }};
2632}