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(Debug, Serialize)]
678pub struct ReceivePaymentRequest {
679 pub prepare_response: PrepareReceiveResponse,
680 pub description: Option<String>,
682 pub use_description_hash: Option<bool>,
684 pub payer_note: Option<String>,
686}
687
688#[derive(Debug, Serialize)]
690pub struct ReceivePaymentResponse {
691 pub destination: String,
694 pub liquid_expiration_blockheight: Option<u32>,
696 pub bitcoin_expiration_blockheight: Option<u32>,
698}
699
700#[derive(Debug, Serialize)]
702pub struct CreateBolt12InvoiceRequest {
703 pub offer: String,
705 pub invoice_request: String,
707}
708
709#[derive(Debug, Serialize, Clone)]
711pub struct CreateBolt12InvoiceResponse {
712 pub invoice: String,
714}
715
716#[derive(Debug, Serialize)]
718pub struct Limits {
719 pub min_sat: u64,
720 pub max_sat: u64,
721 pub max_zero_conf_sat: u64,
722}
723
724#[derive(Debug, Serialize)]
726pub struct LightningPaymentLimitsResponse {
727 pub send: Limits,
729 pub receive: Limits,
731}
732
733#[derive(Debug, Serialize)]
735pub struct OnchainPaymentLimitsResponse {
736 pub send: Limits,
738 pub receive: Limits,
740}
741
742#[derive(Debug, Serialize, Clone)]
744pub struct PrepareSendRequest {
745 pub destination: String,
748 pub amount: Option<PayAmount>,
751 pub disable_mrh: Option<bool>,
753 pub payment_timeout_sec: Option<u64>,
756}
757
758#[derive(Clone, Debug, Serialize)]
760pub enum SendDestination {
761 LiquidAddress {
762 address_data: liquid::LiquidAddressData,
763 bip353_address: Option<String>,
765 },
766 Bolt11 {
767 invoice: LNInvoice,
768 bip353_address: Option<String>,
770 },
771 Bolt12 {
772 offer: LNOffer,
773 receiver_amount_sat: u64,
774 bip353_address: Option<String>,
776 },
777}
778
779#[derive(Debug, Serialize, Clone)]
781pub struct PrepareSendResponse {
782 pub destination: SendDestination,
783 pub amount: Option<PayAmount>,
785 pub fees_sat: Option<u64>,
788 pub estimated_asset_fees: Option<f64>,
792 pub exchange_amount_sat: Option<u64>,
795 pub disable_mrh: Option<bool>,
797 pub payment_timeout_sec: Option<u64>,
799}
800
801#[derive(Debug, Serialize)]
803pub struct SendPaymentRequest {
804 pub prepare_response: PrepareSendResponse,
805 pub use_asset_fees: Option<bool>,
807 pub payer_note: Option<String>,
809}
810
811#[derive(Debug, Serialize)]
813pub struct SendPaymentResponse {
814 pub payment: Payment,
815}
816
817pub(crate) struct SendPaymentViaSwapRequest {
818 pub(crate) invoice: String,
819 pub(crate) bolt12_offer: Option<String>,
820 pub(crate) payment_hash: String,
821 pub(crate) description: Option<String>,
822 pub(crate) receiver_amount_sat: u64,
823 pub(crate) fees_sat: u64,
824}
825
826pub(crate) struct PayLiquidRequest {
827 pub address_data: LiquidAddressData,
828 pub to_asset: String,
829 pub receiver_amount_sat: u64,
830 pub asset_pay_fees: bool,
831 pub fees_sat: Option<u64>,
832}
833
834pub(crate) struct PaySideSwapRequest {
835 pub address_data: LiquidAddressData,
836 pub to_asset: String,
837 pub receiver_amount_sat: u64,
838 pub fees_sat: u64,
839 pub amount: Option<PayAmount>,
840}
841
842#[derive(Debug, Serialize, Clone)]
844pub enum PayAmount {
845 Bitcoin { receiver_amount_sat: u64 },
847
848 Asset {
850 to_asset: String,
852 receiver_amount: f64,
853 estimate_asset_fees: Option<bool>,
854 from_asset: Option<String>,
857 },
858
859 Drain,
861}
862
863impl PayAmount {
864 pub(crate) fn is_sideswap_payment(&self) -> bool {
865 match self {
866 PayAmount::Asset {
867 to_asset,
868 from_asset,
869 ..
870 } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
871 _ => false,
872 }
873 }
874}
875
876#[derive(Debug, Serialize, Clone)]
878pub struct PreparePayOnchainRequest {
879 pub amount: PayAmount,
881 pub fee_rate_sat_per_vbyte: Option<u32>,
883}
884
885#[derive(Debug, Serialize, Clone)]
887pub struct PreparePayOnchainResponse {
888 pub receiver_amount_sat: u64,
889 pub claim_fees_sat: u64,
890 pub total_fees_sat: u64,
891}
892
893#[derive(Debug, Serialize)]
895pub struct PayOnchainRequest {
896 pub address: String,
897 pub prepare_response: PreparePayOnchainResponse,
898}
899
900#[derive(Debug, Serialize)]
902pub struct PrepareRefundRequest {
903 pub swap_address: String,
905 pub refund_address: String,
907 pub fee_rate_sat_per_vbyte: u32,
909}
910
911#[derive(Debug, Serialize)]
913pub struct PrepareRefundResponse {
914 pub tx_vsize: u32,
915 pub tx_fee_sat: u64,
916 pub last_refund_tx_id: Option<String>,
918}
919
920#[derive(Debug, Serialize)]
922pub struct RefundRequest {
923 pub swap_address: String,
925 pub refund_address: String,
927 pub fee_rate_sat_per_vbyte: u32,
929}
930
931#[derive(Debug, Serialize)]
933pub struct RefundResponse {
934 pub refund_tx_id: String,
935}
936
937#[derive(Clone, Debug, Default, Serialize, Deserialize)]
939pub struct AssetBalance {
940 pub asset_id: String,
941 pub balance_sat: u64,
942 pub name: Option<String>,
943 pub ticker: Option<String>,
944 pub balance: Option<f64>,
945}
946
947#[derive(Debug, Serialize, Deserialize, Default)]
948pub struct BlockchainInfo {
949 pub liquid_tip: u32,
950 pub bitcoin_tip: u32,
951}
952
953#[derive(Copy, Clone)]
954pub(crate) struct ChainTips {
955 pub liquid_tip: u32,
956 pub bitcoin_tip: Option<u32>,
957}
958
959#[derive(Debug, Serialize, Deserialize)]
960pub struct WalletInfo {
961 pub balance_sat: u64,
963 pub pending_send_sat: u64,
965 pub pending_receive_sat: u64,
967 pub fingerprint: String,
969 pub pubkey: String,
971 #[serde(default)]
973 pub asset_balances: Vec<AssetBalance>,
974}
975
976impl WalletInfo {
977 pub(crate) fn validate_sufficient_funds(
978 &self,
979 network: LiquidNetwork,
980 amount_sat: u64,
981 fees_sat: Option<u64>,
982 asset_id: &str,
983 ) -> Result<(), PaymentError> {
984 let fees_sat = fees_sat.unwrap_or(0);
985 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
986 ensure_sdk!(
987 amount_sat + fees_sat <= self.balance_sat,
988 PaymentError::InsufficientFunds
989 );
990 } else {
991 match self
992 .asset_balances
993 .iter()
994 .find(|ab| ab.asset_id.eq(asset_id))
995 {
996 Some(asset_balance) => ensure_sdk!(
997 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
998 PaymentError::InsufficientFunds
999 ),
1000 None => return Err(PaymentError::InsufficientFunds),
1001 }
1002 }
1003 Ok(())
1004 }
1005}
1006
1007#[derive(Debug, Serialize, Deserialize)]
1009pub struct GetInfoResponse {
1010 pub wallet_info: WalletInfo,
1012 #[serde(default)]
1014 pub blockchain_info: BlockchainInfo,
1015}
1016
1017#[derive(Clone, Debug, PartialEq)]
1019pub struct SignMessageRequest {
1020 pub message: String,
1021}
1022
1023#[derive(Clone, Debug, PartialEq)]
1025pub struct SignMessageResponse {
1026 pub signature: String,
1027}
1028
1029#[derive(Clone, Debug, PartialEq)]
1031pub struct CheckMessageRequest {
1032 pub message: String,
1034 pub pubkey: String,
1036 pub signature: String,
1038}
1039
1040#[derive(Clone, Debug, PartialEq)]
1042pub struct CheckMessageResponse {
1043 pub is_valid: bool,
1046}
1047
1048#[derive(Debug, Serialize)]
1050pub struct BackupRequest {
1051 pub backup_path: Option<String>,
1058}
1059
1060#[derive(Debug, Serialize)]
1062pub struct RestoreRequest {
1063 pub backup_path: Option<String>,
1064}
1065
1066#[derive(Default)]
1068pub struct ListPaymentsRequest {
1069 pub filters: Option<Vec<PaymentType>>,
1070 pub states: Option<Vec<PaymentState>>,
1071 pub from_timestamp: Option<i64>,
1073 pub to_timestamp: Option<i64>,
1075 pub offset: Option<u32>,
1076 pub limit: Option<u32>,
1077 pub details: Option<ListPaymentDetails>,
1078 pub sort_ascending: Option<bool>,
1079}
1080
1081#[derive(Debug, Serialize)]
1083pub enum ListPaymentDetails {
1084 Liquid {
1086 asset_id: Option<String>,
1088 destination: Option<String>,
1090 },
1091
1092 Bitcoin {
1094 address: Option<String>,
1096 },
1097}
1098
1099#[derive(Debug, Serialize)]
1101pub enum GetPaymentRequest {
1102 PaymentHash { payment_hash: String },
1104 SwapId { swap_id: String },
1106}
1107
1108#[sdk_macros::async_trait]
1110pub(crate) trait BlockListener: Send + Sync {
1111 async fn on_bitcoin_block(&self, height: u32);
1112 async fn on_liquid_block(&self, height: u32);
1113}
1114
1115#[derive(Clone, Debug)]
1117pub enum Swap {
1118 Chain(ChainSwap),
1119 Send(SendSwap),
1120 Receive(ReceiveSwap),
1121}
1122impl Swap {
1123 pub(crate) fn id(&self) -> String {
1124 match &self {
1125 Swap::Chain(ChainSwap { id, .. })
1126 | Swap::Send(SendSwap { id, .. })
1127 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1128 }
1129 }
1130
1131 pub(crate) fn version(&self) -> u64 {
1132 match self {
1133 Swap::Chain(ChainSwap { metadata, .. })
1134 | Swap::Send(SendSwap { metadata, .. })
1135 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1136 }
1137 }
1138
1139 pub(crate) fn set_version(&mut self, version: u64) {
1140 match self {
1141 Swap::Chain(chain_swap) => {
1142 chain_swap.metadata.version = version;
1143 }
1144 Swap::Send(send_swap) => {
1145 send_swap.metadata.version = version;
1146 }
1147 Swap::Receive(receive_swap) => {
1148 receive_swap.metadata.version = version;
1149 }
1150 }
1151 }
1152
1153 pub(crate) fn last_updated_at(&self) -> u32 {
1154 match self {
1155 Swap::Chain(ChainSwap { metadata, .. })
1156 | Swap::Send(SendSwap { metadata, .. })
1157 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1158 }
1159 }
1160}
1161impl From<ChainSwap> for Swap {
1162 fn from(swap: ChainSwap) -> Self {
1163 Self::Chain(swap)
1164 }
1165}
1166impl From<SendSwap> for Swap {
1167 fn from(swap: SendSwap) -> Self {
1168 Self::Send(swap)
1169 }
1170}
1171impl From<ReceiveSwap> for Swap {
1172 fn from(swap: ReceiveSwap) -> Self {
1173 Self::Receive(swap)
1174 }
1175}
1176
1177#[derive(Clone, Debug)]
1178pub(crate) enum SwapScriptV2 {
1179 Bitcoin(BtcSwapScript),
1180 Liquid(LBtcSwapScript),
1181}
1182impl SwapScriptV2 {
1183 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1184 match self {
1185 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1186 _ => Err(anyhow!("Invalid chain")),
1187 }
1188 }
1189
1190 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1191 match self {
1192 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1193 _ => Err(anyhow!("Invalid chain")),
1194 }
1195 }
1196}
1197
1198#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1199pub enum Direction {
1200 Incoming = 0,
1201 Outgoing = 1,
1202}
1203impl ToSql for Direction {
1204 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1205 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1206 }
1207}
1208impl FromSql for Direction {
1209 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1210 match value {
1211 ValueRef::Integer(i) => match i as u8 {
1212 0 => Ok(Direction::Incoming),
1213 1 => Ok(Direction::Outgoing),
1214 _ => Err(FromSqlError::OutOfRange(i)),
1215 },
1216 _ => Err(FromSqlError::InvalidType),
1217 }
1218 }
1219}
1220
1221#[derive(Clone, Debug, Default)]
1222pub(crate) struct SwapMetadata {
1223 pub(crate) version: u64,
1225 pub(crate) last_updated_at: u32,
1226 pub(crate) is_local: bool,
1227}
1228
1229#[derive(Clone, Debug, Derivative)]
1233#[derivative(PartialEq)]
1234pub struct ChainSwap {
1235 pub(crate) id: String,
1236 pub(crate) direction: Direction,
1237 pub(crate) claim_address: Option<String>,
1240 pub(crate) lockup_address: String,
1241 pub(crate) refund_address: Option<String>,
1243 pub(crate) timeout_block_height: u32,
1245 pub(crate) claim_timeout_block_height: u32,
1247 pub(crate) preimage: String,
1248 pub(crate) description: Option<String>,
1249 pub(crate) payer_amount_sat: u64,
1251 pub(crate) actual_payer_amount_sat: Option<u64>,
1254 pub(crate) receiver_amount_sat: u64,
1256 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1258 pub(crate) claim_fees_sat: u64,
1259 pub(crate) pair_fees_json: String,
1261 pub(crate) accept_zero_conf: bool,
1262 pub(crate) create_response_json: String,
1264 pub(crate) server_lockup_tx_id: Option<String>,
1266 pub(crate) user_lockup_tx_id: Option<String>,
1268 pub(crate) claim_tx_id: Option<String>,
1270 pub(crate) refund_tx_id: Option<String>,
1272 pub(crate) created_at: u32,
1273 pub(crate) state: PaymentState,
1274 pub(crate) claim_private_key: String,
1275 pub(crate) refund_private_key: String,
1276 pub(crate) auto_accepted_fees: bool,
1277 #[derivative(PartialEq = "ignore")]
1279 pub(crate) metadata: SwapMetadata,
1280}
1281impl ChainSwap {
1282 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1283 utils::decode_keypair(&self.claim_private_key)
1284 }
1285
1286 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1287 utils::decode_keypair(&self.refund_private_key)
1288 }
1289
1290 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1291 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1292 serde_json::from_str(&self.create_response_json).map_err(|e| {
1293 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1294 })?;
1295
1296 Ok(CreateChainResponse {
1297 id: self.id.clone(),
1298 claim_details: internal_create_response.claim_details,
1299 lockup_details: internal_create_response.lockup_details,
1300 })
1301 }
1302
1303 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1304 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1305 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1306
1307 Ok(pair)
1308 }
1309
1310 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1311 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1312 let our_pubkey = self.get_claim_keypair()?.public_key();
1313 let swap_script = match self.direction {
1314 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1315 Side::Claim,
1316 chain_swap_details,
1317 our_pubkey.into(),
1318 )?),
1319 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1320 Side::Claim,
1321 chain_swap_details,
1322 our_pubkey.into(),
1323 )?),
1324 };
1325 Ok(swap_script)
1326 }
1327
1328 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1329 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1330 let our_pubkey = self.get_refund_keypair()?.public_key();
1331 let swap_script = match self.direction {
1332 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1333 Side::Lockup,
1334 chain_swap_details,
1335 our_pubkey.into(),
1336 )?),
1337 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1338 Side::Lockup,
1339 chain_swap_details,
1340 our_pubkey.into(),
1341 )?),
1342 };
1343 Ok(swap_script)
1344 }
1345
1346 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1348 &self,
1349 network: LiquidNetwork,
1350 ) -> SdkResult<ScriptBuf> {
1351 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1352 let script_pubkey = swap_script
1353 .to_address(network.as_bitcoin_chain())
1354 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1355 .script_pubkey();
1356 Ok(script_pubkey)
1357 }
1358
1359 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1360 RefundableSwap {
1361 swap_address: self.lockup_address.clone(),
1362 timestamp: self.created_at,
1363 amount_sat,
1364 last_refund_tx_id: self.refund_tx_id.clone(),
1365 }
1366 }
1367
1368 pub(crate) fn from_boltz_struct_to_json(
1369 create_response: &CreateChainResponse,
1370 expected_swap_id: &str,
1371 ) -> Result<String, PaymentError> {
1372 let internal_create_response =
1373 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1374 create_response,
1375 expected_swap_id,
1376 )?;
1377
1378 let create_response_json =
1379 serde_json::to_string(&internal_create_response).map_err(|e| {
1380 PaymentError::Generic {
1381 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1382 }
1383 })?;
1384
1385 Ok(create_response_json)
1386 }
1387
1388 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1389 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1390 }
1391}
1392
1393#[derive(Clone, Debug, Default)]
1394pub(crate) struct ChainSwapUpdate {
1395 pub(crate) swap_id: String,
1396 pub(crate) to_state: PaymentState,
1397 pub(crate) server_lockup_tx_id: Option<String>,
1398 pub(crate) user_lockup_tx_id: Option<String>,
1399 pub(crate) claim_address: Option<String>,
1400 pub(crate) claim_tx_id: Option<String>,
1401 pub(crate) refund_tx_id: Option<String>,
1402}
1403
1404#[derive(Clone, Debug, Derivative)]
1406#[derivative(PartialEq)]
1407pub struct SendSwap {
1408 pub(crate) id: String,
1409 pub(crate) invoice: String,
1411 pub(crate) bolt12_offer: Option<String>,
1413 pub(crate) payment_hash: Option<String>,
1414 pub(crate) destination_pubkey: Option<String>,
1415 pub(crate) description: Option<String>,
1416 pub(crate) preimage: Option<String>,
1417 pub(crate) payer_amount_sat: u64,
1418 pub(crate) receiver_amount_sat: u64,
1419 pub(crate) pair_fees_json: String,
1421 pub(crate) create_response_json: String,
1423 pub(crate) lockup_tx_id: Option<String>,
1425 pub(crate) refund_address: Option<String>,
1427 pub(crate) refund_tx_id: Option<String>,
1429 pub(crate) created_at: u32,
1430 pub(crate) timeout_block_height: u64,
1431 pub(crate) state: PaymentState,
1432 pub(crate) refund_private_key: String,
1433 #[derivative(PartialEq = "ignore")]
1435 pub(crate) metadata: SwapMetadata,
1436}
1437impl SendSwap {
1438 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1439 utils::decode_keypair(&self.refund_private_key)
1440 }
1441
1442 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1443 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1444 serde_json::from_str(&self.create_response_json).map_err(|e| {
1445 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1446 })?;
1447
1448 let res = CreateSubmarineResponse {
1449 id: self.id.clone(),
1450 accept_zero_conf: internal_create_response.accept_zero_conf,
1451 address: internal_create_response.address.clone(),
1452 bip21: internal_create_response.bip21.clone(),
1453 claim_public_key: crate::utils::json_to_pubkey(
1454 &internal_create_response.claim_public_key,
1455 )?,
1456 expected_amount: internal_create_response.expected_amount,
1457 referral_id: internal_create_response.referral_id,
1458 swap_tree: internal_create_response.swap_tree.clone().into(),
1459 timeout_block_height: internal_create_response.timeout_block_height,
1460 blinding_key: internal_create_response.blinding_key.clone(),
1461 };
1462 Ok(res)
1463 }
1464
1465 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1466 LBtcSwapScript::submarine_from_swap_resp(
1467 &self.get_boltz_create_response()?,
1468 self.get_refund_keypair()?.public_key().into(),
1469 )
1470 .map_err(|e| {
1471 SdkError::generic(format!(
1472 "Failed to create swap script for Send Swap {}: {e:?}",
1473 self.id
1474 ))
1475 })
1476 }
1477
1478 pub(crate) fn from_boltz_struct_to_json(
1479 create_response: &CreateSubmarineResponse,
1480 expected_swap_id: &str,
1481 ) -> Result<String, PaymentError> {
1482 let internal_create_response =
1483 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1484 create_response,
1485 expected_swap_id,
1486 )?;
1487
1488 let create_response_json =
1489 serde_json::to_string(&internal_create_response).map_err(|e| {
1490 PaymentError::Generic {
1491 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1492 }
1493 })?;
1494
1495 Ok(create_response_json)
1496 }
1497}
1498
1499#[derive(Clone, Debug, Derivative)]
1501#[derivative(PartialEq)]
1502pub struct ReceiveSwap {
1503 pub(crate) id: String,
1504 pub(crate) preimage: String,
1505 pub(crate) create_response_json: String,
1507 pub(crate) claim_private_key: String,
1508 pub(crate) invoice: String,
1509 pub(crate) bolt12_offer: Option<String>,
1511 pub(crate) payment_hash: Option<String>,
1512 pub(crate) destination_pubkey: Option<String>,
1513 pub(crate) description: Option<String>,
1514 pub(crate) payer_note: Option<String>,
1515 pub(crate) payer_amount_sat: u64,
1517 pub(crate) receiver_amount_sat: u64,
1518 pub(crate) pair_fees_json: String,
1520 pub(crate) claim_fees_sat: u64,
1521 pub(crate) claim_address: Option<String>,
1523 pub(crate) claim_tx_id: Option<String>,
1525 pub(crate) lockup_tx_id: Option<String>,
1527 pub(crate) mrh_address: String,
1529 pub(crate) mrh_tx_id: Option<String>,
1531 pub(crate) created_at: u32,
1534 pub(crate) timeout_block_height: u32,
1535 pub(crate) state: PaymentState,
1536 #[derivative(PartialEq = "ignore")]
1538 pub(crate) metadata: SwapMetadata,
1539}
1540impl ReceiveSwap {
1541 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1542 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1543 }
1544
1545 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1546 Ok(self
1547 .get_swap_script()?
1548 .funding_addrs
1549 .ok_or(anyhow!("No funding address found"))?
1550 .script_pubkey())
1551 }
1552
1553 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1554 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1555 serde_json::from_str(&self.create_response_json).map_err(|e| {
1556 PaymentError::Generic {
1557 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1558 }
1559 })?;
1560
1561 let res = CreateReverseResponse {
1562 id: self.id.clone(),
1563 invoice: Some(self.invoice.clone()),
1564 swap_tree: internal_create_response.swap_tree.clone().into(),
1565 lockup_address: internal_create_response.lockup_address.clone(),
1566 refund_public_key: crate::utils::json_to_pubkey(
1567 &internal_create_response.refund_public_key,
1568 )?,
1569 timeout_block_height: internal_create_response.timeout_block_height,
1570 onchain_amount: internal_create_response.onchain_amount,
1571 blinding_key: internal_create_response.blinding_key.clone(),
1572 };
1573 Ok(res)
1574 }
1575
1576 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1577 let keypair = self.get_claim_keypair()?;
1578 let create_response =
1579 self.get_boltz_create_response()
1580 .map_err(|e| PaymentError::Generic {
1581 err: format!(
1582 "Failed to create swap script for Receive Swap {}: {e:?}",
1583 self.id
1584 ),
1585 })?;
1586 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1587 .map_err(|e| PaymentError::Generic {
1588 err: format!(
1589 "Failed to create swap script for Receive Swap {}: {e:?}",
1590 self.id
1591 ),
1592 })
1593 }
1594
1595 pub(crate) fn from_boltz_struct_to_json(
1596 create_response: &CreateReverseResponse,
1597 expected_swap_id: &str,
1598 expected_invoice: Option<&str>,
1599 ) -> Result<String, PaymentError> {
1600 let internal_create_response =
1601 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1602 create_response,
1603 expected_swap_id,
1604 expected_invoice,
1605 )?;
1606
1607 let create_response_json =
1608 serde_json::to_string(&internal_create_response).map_err(|e| {
1609 PaymentError::Generic {
1610 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1611 }
1612 })?;
1613
1614 Ok(create_response_json)
1615 }
1616}
1617
1618#[derive(Clone, Debug, PartialEq, Serialize)]
1620pub struct RefundableSwap {
1621 pub swap_address: String,
1622 pub timestamp: u32,
1623 pub amount_sat: u64,
1625 pub last_refund_tx_id: Option<String>,
1627}
1628
1629#[derive(Clone, Debug, Derivative)]
1631#[derivative(PartialEq)]
1632pub(crate) struct Bolt12Offer {
1633 pub(crate) id: String,
1635 pub(crate) description: String,
1637 pub(crate) private_key: String,
1639 pub(crate) webhook_url: Option<String>,
1641 pub(crate) created_at: u32,
1643}
1644impl Bolt12Offer {
1645 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1646 utils::decode_keypair(&self.private_key)
1647 }
1648}
1649impl TryFrom<Bolt12Offer> for Offer {
1650 type Error = SdkError;
1651
1652 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1653 Offer::from_str(&val.id)
1654 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1655 }
1656}
1657
1658#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1660#[strum(serialize_all = "lowercase")]
1661pub enum PaymentState {
1662 #[default]
1663 Created = 0,
1664
1665 Pending = 1,
1685
1686 Complete = 2,
1698
1699 Failed = 3,
1707
1708 TimedOut = 4,
1713
1714 Refundable = 5,
1719
1720 RefundPending = 6,
1726
1727 WaitingFeeAcceptance = 7,
1739}
1740
1741impl ToSql for PaymentState {
1742 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1743 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1744 }
1745}
1746impl FromSql for PaymentState {
1747 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1748 match value {
1749 ValueRef::Integer(i) => match i as u8 {
1750 0 => Ok(PaymentState::Created),
1751 1 => Ok(PaymentState::Pending),
1752 2 => Ok(PaymentState::Complete),
1753 3 => Ok(PaymentState::Failed),
1754 4 => Ok(PaymentState::TimedOut),
1755 5 => Ok(PaymentState::Refundable),
1756 6 => Ok(PaymentState::RefundPending),
1757 7 => Ok(PaymentState::WaitingFeeAcceptance),
1758 _ => Err(FromSqlError::OutOfRange(i)),
1759 },
1760 _ => Err(FromSqlError::InvalidType),
1761 }
1762 }
1763}
1764
1765impl PaymentState {
1766 pub(crate) fn is_refundable(&self) -> bool {
1767 matches!(
1768 self,
1769 PaymentState::Refundable
1770 | PaymentState::RefundPending
1771 | PaymentState::WaitingFeeAcceptance
1772 )
1773 }
1774}
1775
1776#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1777#[strum(serialize_all = "lowercase")]
1778pub enum PaymentType {
1779 Receive = 0,
1780 Send = 1,
1781}
1782impl From<Direction> for PaymentType {
1783 fn from(value: Direction) -> Self {
1784 match value {
1785 Direction::Incoming => Self::Receive,
1786 Direction::Outgoing => Self::Send,
1787 }
1788 }
1789}
1790impl ToSql for PaymentType {
1791 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1792 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1793 }
1794}
1795impl FromSql for PaymentType {
1796 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1797 match value {
1798 ValueRef::Integer(i) => match i as u8 {
1799 0 => Ok(PaymentType::Receive),
1800 1 => Ok(PaymentType::Send),
1801 _ => Err(FromSqlError::OutOfRange(i)),
1802 },
1803 _ => Err(FromSqlError::InvalidType),
1804 }
1805 }
1806}
1807
1808#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1809pub enum PaymentStatus {
1810 Pending = 0,
1811 Complete = 1,
1812}
1813impl ToSql for PaymentStatus {
1814 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1815 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1816 }
1817}
1818impl FromSql for PaymentStatus {
1819 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1820 match value {
1821 ValueRef::Integer(i) => match i as u8 {
1822 0 => Ok(PaymentStatus::Pending),
1823 1 => Ok(PaymentStatus::Complete),
1824 _ => Err(FromSqlError::OutOfRange(i)),
1825 },
1826 _ => Err(FromSqlError::InvalidType),
1827 }
1828 }
1829}
1830
1831#[derive(Debug, Clone, Serialize)]
1832pub struct PaymentTxData {
1833 pub tx_id: String,
1835
1836 pub timestamp: Option<u32>,
1838
1839 pub fees_sat: u64,
1841
1842 pub is_confirmed: bool,
1844
1845 pub unblinding_data: Option<String>,
1848}
1849
1850#[derive(Debug, Clone, Serialize)]
1851pub enum PaymentSwapType {
1852 Receive,
1853 Send,
1854 Chain,
1855}
1856
1857#[derive(Debug, Clone, Serialize)]
1858pub struct PaymentSwapData {
1859 pub swap_id: String,
1860
1861 pub swap_type: PaymentSwapType,
1862
1863 pub created_at: u32,
1865
1866 pub expiration_blockheight: u32,
1869
1870 pub claim_expiration_blockheight: Option<u32>,
1872
1873 pub preimage: Option<String>,
1874 pub invoice: Option<String>,
1875 pub bolt12_offer: Option<String>,
1876 pub payment_hash: Option<String>,
1877 pub destination_pubkey: Option<String>,
1878 pub description: String,
1879 pub payer_note: Option<String>,
1880
1881 pub payer_amount_sat: u64,
1883
1884 pub receiver_amount_sat: u64,
1886
1887 pub swapper_fees_sat: u64,
1889
1890 pub refund_tx_id: Option<String>,
1891 pub refund_tx_amount_sat: Option<u64>,
1892
1893 pub bitcoin_address: Option<String>,
1896
1897 pub status: PaymentState,
1899}
1900
1901#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1903pub struct LnUrlInfo {
1904 pub ln_address: Option<String>,
1905 pub lnurl_pay_comment: Option<String>,
1906 pub lnurl_pay_domain: Option<String>,
1907 pub lnurl_pay_metadata: Option<String>,
1908 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1909 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1910 pub lnurl_withdraw_endpoint: Option<String>,
1911}
1912
1913#[derive(Debug, Clone, Serialize)]
1917pub struct AssetMetadata {
1918 pub asset_id: String,
1920 pub name: String,
1922 pub ticker: String,
1924 pub precision: u8,
1927 pub fiat_id: Option<String>,
1929}
1930
1931impl AssetMetadata {
1932 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1933 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1934 }
1935
1936 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1937 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1938 }
1939}
1940
1941#[derive(Clone, Debug, PartialEq, Serialize)]
1944pub struct AssetInfo {
1945 pub name: String,
1947 pub ticker: String,
1949 pub amount: f64,
1952 pub fees: Option<f64>,
1955}
1956
1957#[derive(Debug, Clone, PartialEq, Serialize)]
1959#[allow(clippy::large_enum_variant)]
1960pub enum PaymentDetails {
1961 Lightning {
1963 swap_id: String,
1964
1965 description: String,
1967
1968 liquid_expiration_blockheight: u32,
1970
1971 preimage: Option<String>,
1973
1974 invoice: Option<String>,
1978
1979 bolt12_offer: Option<String>,
1980
1981 payment_hash: Option<String>,
1983
1984 destination_pubkey: Option<String>,
1986
1987 lnurl_info: Option<LnUrlInfo>,
1989
1990 bip353_address: Option<String>,
1992
1993 payer_note: Option<String>,
1995
1996 claim_tx_id: Option<String>,
1998
1999 refund_tx_id: Option<String>,
2001
2002 refund_tx_amount_sat: Option<u64>,
2004 },
2005 Liquid {
2007 destination: String,
2009
2010 description: String,
2012
2013 asset_id: String,
2015
2016 asset_info: Option<AssetInfo>,
2018
2019 lnurl_info: Option<LnUrlInfo>,
2021
2022 bip353_address: Option<String>,
2024
2025 payer_note: Option<String>,
2027 },
2028 Bitcoin {
2030 swap_id: String,
2031
2032 bitcoin_address: String,
2034
2035 description: String,
2037
2038 auto_accepted_fees: bool,
2042
2043 liquid_expiration_blockheight: u32,
2045
2046 bitcoin_expiration_blockheight: u32,
2048
2049 lockup_tx_id: Option<String>,
2051
2052 claim_tx_id: Option<String>,
2054
2055 refund_tx_id: Option<String>,
2057
2058 refund_tx_amount_sat: Option<u64>,
2060 },
2061}
2062
2063impl PaymentDetails {
2064 pub(crate) fn get_swap_id(&self) -> Option<String> {
2065 match self {
2066 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2067 Some(swap_id.clone())
2068 }
2069 Self::Liquid { .. } => None,
2070 }
2071 }
2072
2073 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2074 match self {
2075 Self::Lightning {
2076 refund_tx_amount_sat,
2077 ..
2078 }
2079 | Self::Bitcoin {
2080 refund_tx_amount_sat,
2081 ..
2082 } => *refund_tx_amount_sat,
2083 Self::Liquid { .. } => None,
2084 }
2085 }
2086
2087 pub(crate) fn get_description(&self) -> Option<String> {
2088 match self {
2089 Self::Lightning { description, .. }
2090 | Self::Bitcoin { description, .. }
2091 | Self::Liquid { description, .. } => Some(description.clone()),
2092 }
2093 }
2094
2095 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2096 match self {
2097 Self::Liquid { asset_id, .. } => {
2098 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2099 }
2100 _ => true,
2101 }
2102 }
2103}
2104
2105#[derive(Debug, Clone, PartialEq, Serialize)]
2109pub struct Payment {
2110 pub destination: Option<String>,
2113
2114 pub tx_id: Option<String>,
2115
2116 pub unblinding_data: Option<String>,
2119
2120 pub timestamp: u32,
2126
2127 pub amount_sat: u64,
2131
2132 pub fees_sat: u64,
2146
2147 pub swapper_fees_sat: Option<u64>,
2150
2151 pub payment_type: PaymentType,
2153
2154 pub status: PaymentState,
2160
2161 pub details: PaymentDetails,
2164}
2165impl Payment {
2166 pub(crate) fn from_pending_swap(
2167 swap: PaymentSwapData,
2168 payment_type: PaymentType,
2169 payment_details: PaymentDetails,
2170 ) -> Payment {
2171 let amount_sat = match payment_type {
2172 PaymentType::Receive => swap.receiver_amount_sat,
2173 PaymentType::Send => swap.payer_amount_sat,
2174 };
2175
2176 Payment {
2177 destination: swap.invoice.clone(),
2178 tx_id: None,
2179 unblinding_data: None,
2180 timestamp: swap.created_at,
2181 amount_sat,
2182 fees_sat: swap
2183 .payer_amount_sat
2184 .saturating_sub(swap.receiver_amount_sat),
2185 swapper_fees_sat: Some(swap.swapper_fees_sat),
2186 payment_type,
2187 status: swap.status,
2188 details: payment_details,
2189 }
2190 }
2191
2192 pub(crate) fn from_tx_data(
2193 tx: PaymentTxData,
2194 balance: PaymentTxBalance,
2195 swap: Option<PaymentSwapData>,
2196 details: PaymentDetails,
2197 ) -> Payment {
2198 let (amount_sat, fees_sat) = match swap.as_ref() {
2199 Some(s) => match balance.payment_type {
2200 PaymentType::Receive => (
2204 balance.amount,
2205 s.payer_amount_sat.saturating_sub(balance.amount),
2206 ),
2207 PaymentType::Send => (
2208 s.receiver_amount_sat,
2209 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2210 ),
2211 },
2212 None => {
2213 let (amount_sat, fees_sat) = match balance.payment_type {
2214 PaymentType::Receive => (balance.amount, 0),
2215 PaymentType::Send => (balance.amount, tx.fees_sat),
2216 };
2217 match details {
2220 PaymentDetails::Liquid {
2221 asset_info: Some(ref asset_info),
2222 ..
2223 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2224 _ => (amount_sat, fees_sat),
2225 }
2226 }
2227 };
2228 Payment {
2229 tx_id: Some(tx.tx_id),
2230 unblinding_data: tx.unblinding_data,
2231 destination: match &swap {
2235 Some(PaymentSwapData {
2236 swap_type: PaymentSwapType::Receive,
2237 invoice,
2238 ..
2239 }) => invoice.clone(),
2240 Some(PaymentSwapData {
2241 swap_type: PaymentSwapType::Send,
2242 invoice,
2243 bolt12_offer,
2244 ..
2245 }) => bolt12_offer.clone().or(invoice.clone()),
2246 Some(PaymentSwapData {
2247 swap_type: PaymentSwapType::Chain,
2248 bitcoin_address,
2249 ..
2250 }) => bitcoin_address.clone(),
2251 _ => match &details {
2252 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2253 _ => None,
2254 },
2255 },
2256 timestamp: tx
2257 .timestamp
2258 .or(swap.as_ref().map(|s| s.created_at))
2259 .unwrap_or(utils::now()),
2260 amount_sat,
2261 fees_sat,
2262 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2263 payment_type: balance.payment_type,
2264 status: match &swap {
2265 Some(swap) => swap.status,
2266 None => match tx.is_confirmed {
2267 true => PaymentState::Complete,
2268 false => PaymentState::Pending,
2269 },
2270 },
2271 details,
2272 }
2273 }
2274
2275 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2276 match self.details.clone() {
2277 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2278 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2279 PaymentDetails::Liquid { .. } => None,
2280 }
2281 .flatten()
2282 }
2283}
2284
2285#[derive(Deserialize, Serialize, Clone, Debug)]
2287#[serde(rename_all = "camelCase")]
2288pub struct RecommendedFees {
2289 pub fastest_fee: u64,
2290 pub half_hour_fee: u64,
2291 pub hour_fee: u64,
2292 pub economy_fee: u64,
2293 pub minimum_fee: u64,
2294}
2295
2296#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2298pub enum BuyBitcoinProvider {
2299 #[strum(serialize = "moonpay")]
2300 Moonpay,
2301}
2302
2303#[derive(Debug, Serialize)]
2305pub struct PrepareBuyBitcoinRequest {
2306 pub provider: BuyBitcoinProvider,
2307 pub amount_sat: u64,
2308}
2309
2310#[derive(Clone, Debug, Serialize)]
2312pub struct PrepareBuyBitcoinResponse {
2313 pub provider: BuyBitcoinProvider,
2314 pub amount_sat: u64,
2315 pub fees_sat: u64,
2316}
2317
2318#[derive(Clone, Debug, Serialize)]
2320pub struct BuyBitcoinRequest {
2321 pub prepare_response: PrepareBuyBitcoinResponse,
2322 pub redirect_url: Option<String>,
2326}
2327
2328#[derive(Clone, Debug)]
2330pub struct LogEntry {
2331 pub line: String,
2332 pub level: String,
2333}
2334
2335#[derive(Clone, Debug, Serialize, Deserialize)]
2336struct InternalLeaf {
2337 pub output: String,
2338 pub version: u8,
2339}
2340impl From<InternalLeaf> for Leaf {
2341 fn from(value: InternalLeaf) -> Self {
2342 Leaf {
2343 output: value.output,
2344 version: value.version,
2345 }
2346 }
2347}
2348impl From<Leaf> for InternalLeaf {
2349 fn from(value: Leaf) -> Self {
2350 InternalLeaf {
2351 output: value.output,
2352 version: value.version,
2353 }
2354 }
2355}
2356
2357#[derive(Clone, Debug, Serialize, Deserialize)]
2358pub(super) struct InternalSwapTree {
2359 claim_leaf: InternalLeaf,
2360 refund_leaf: InternalLeaf,
2361}
2362impl From<InternalSwapTree> for SwapTree {
2363 fn from(value: InternalSwapTree) -> Self {
2364 SwapTree {
2365 claim_leaf: value.claim_leaf.into(),
2366 refund_leaf: value.refund_leaf.into(),
2367 }
2368 }
2369}
2370impl From<SwapTree> for InternalSwapTree {
2371 fn from(value: SwapTree) -> Self {
2372 InternalSwapTree {
2373 claim_leaf: value.claim_leaf.into(),
2374 refund_leaf: value.refund_leaf.into(),
2375 }
2376 }
2377}
2378
2379#[derive(Debug, Serialize)]
2381pub struct PrepareLnUrlPayRequest {
2382 pub data: LnUrlPayRequestData,
2384 pub amount: PayAmount,
2386 pub bip353_address: Option<String>,
2389 pub comment: Option<String>,
2392 pub validate_success_action_url: Option<bool>,
2395}
2396
2397#[derive(Debug, Serialize)]
2399pub struct PrepareLnUrlPayResponse {
2400 pub destination: SendDestination,
2402 pub fees_sat: u64,
2404 pub data: LnUrlPayRequestData,
2406 pub amount: PayAmount,
2408 pub comment: Option<String>,
2411 pub success_action: Option<SuccessAction>,
2414}
2415
2416#[derive(Debug, Serialize)]
2418pub struct LnUrlPayRequest {
2419 pub prepare_response: PrepareLnUrlPayResponse,
2421}
2422
2423#[derive(Serialize)]
2435#[allow(clippy::large_enum_variant)]
2436pub enum LnUrlPayResult {
2437 EndpointSuccess { data: LnUrlPaySuccessData },
2438 EndpointError { data: LnUrlErrorData },
2439 PayError { data: LnUrlPayErrorData },
2440}
2441
2442#[derive(Serialize)]
2443pub struct LnUrlPaySuccessData {
2444 pub payment: Payment,
2445 pub success_action: Option<SuccessActionProcessed>,
2446}
2447
2448#[derive(Debug, Clone)]
2449pub enum Transaction {
2450 Liquid(boltz_client::elements::Transaction),
2451 Bitcoin(boltz_client::bitcoin::Transaction),
2452}
2453
2454impl Transaction {
2455 pub(crate) fn txid(&self) -> String {
2456 match self {
2457 Transaction::Liquid(tx) => tx.txid().to_hex(),
2458 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2459 }
2460 }
2461}
2462
2463#[derive(Debug, Clone)]
2464pub enum Utxo {
2465 Liquid(
2466 Box<(
2467 boltz_client::elements::OutPoint,
2468 boltz_client::elements::TxOut,
2469 )>,
2470 ),
2471 Bitcoin(
2472 (
2473 boltz_client::bitcoin::OutPoint,
2474 boltz_client::bitcoin::TxOut,
2475 ),
2476 ),
2477}
2478
2479impl Utxo {
2480 pub(crate) fn as_bitcoin(
2481 &self,
2482 ) -> Option<&(
2483 boltz_client::bitcoin::OutPoint,
2484 boltz_client::bitcoin::TxOut,
2485 )> {
2486 match self {
2487 Utxo::Liquid(_) => None,
2488 Utxo::Bitcoin(utxo) => Some(utxo),
2489 }
2490 }
2491
2492 pub(crate) fn as_liquid(
2493 &self,
2494 ) -> Option<
2495 Box<(
2496 boltz_client::elements::OutPoint,
2497 boltz_client::elements::TxOut,
2498 )>,
2499 > {
2500 match self {
2501 Utxo::Bitcoin(_) => None,
2502 Utxo::Liquid(utxo) => Some(utxo.clone()),
2503 }
2504 }
2505}
2506
2507#[derive(Debug, Clone)]
2509pub struct FetchPaymentProposedFeesRequest {
2510 pub swap_id: String,
2511}
2512
2513#[derive(Debug, Clone, Serialize)]
2515pub struct FetchPaymentProposedFeesResponse {
2516 pub swap_id: String,
2517 pub fees_sat: u64,
2518 pub payer_amount_sat: u64,
2520 pub receiver_amount_sat: u64,
2522}
2523
2524#[derive(Debug, Clone)]
2526pub struct AcceptPaymentProposedFeesRequest {
2527 pub response: FetchPaymentProposedFeesResponse,
2528}
2529
2530#[derive(Clone, Debug)]
2531pub struct History<T> {
2532 pub txid: T,
2533 pub height: i32,
2538}
2539pub(crate) type LBtcHistory = History<elements::Txid>;
2540pub(crate) type BtcHistory = History<bitcoin::Txid>;
2541
2542impl<T> History<T> {
2543 pub(crate) fn confirmed(&self) -> bool {
2544 self.height > 0
2545 }
2546}
2547#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2548impl From<electrum_client::GetHistoryRes> for BtcHistory {
2549 fn from(value: electrum_client::GetHistoryRes) -> Self {
2550 Self {
2551 txid: value.tx_hash,
2552 height: value.height,
2553 }
2554 }
2555}
2556impl From<lwk_wollet::History> for LBtcHistory {
2557 fn from(value: lwk_wollet::History) -> Self {
2558 Self::from(&value)
2559 }
2560}
2561impl From<&lwk_wollet::History> for LBtcHistory {
2562 fn from(value: &lwk_wollet::History) -> Self {
2563 Self {
2564 txid: value.txid,
2565 height: value.height,
2566 }
2567 }
2568}
2569pub(crate) type BtcScript = bitcoin::ScriptBuf;
2570pub(crate) type LBtcScript = elements::Script;
2571
2572#[derive(Clone, Debug)]
2573pub struct BtcScriptBalance {
2574 pub confirmed: u64,
2576 pub unconfirmed: i64,
2580}
2581#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2582impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2583 fn from(val: electrum_client::GetBalanceRes) -> Self {
2584 Self {
2585 confirmed: val.confirmed,
2586 unconfirmed: val.unconfirmed,
2587 }
2588 }
2589}
2590
2591pub(crate) struct GetSyncContextRequest {
2592 pub partial_sync: Option<bool>,
2593 pub last_liquid_tip: u32,
2594 pub last_bitcoin_tip: u32,
2595}
2596
2597pub(crate) struct SyncContext {
2598 pub maybe_liquid_tip: Option<u32>,
2599 pub maybe_bitcoin_tip: Option<u32>,
2600 pub recoverable_swaps: Vec<Swap>,
2601 pub is_new_liquid_block: bool,
2602 pub is_new_bitcoin_block: bool,
2603}
2604
2605pub(crate) struct TaskHandle {
2606 pub name: String,
2607 pub handle: tokio::task::JoinHandle<()>,
2608}
2609
2610#[macro_export]
2611macro_rules! get_updated_fields {
2612 ($($var:ident),* $(,)?) => {{
2613 let mut options = Vec::new();
2614 $(
2615 if $var.is_some() {
2616 options.push(stringify!($var).to_string());
2617 }
2618 )*
2619 match options.len() > 0 {
2620 true => Some(options),
2621 false => None,
2622 }
2623 }};
2624}