1use anyhow::{anyhow, bail, Result};
2use bitcoin::{bip32, ScriptBuf};
3use boltz_client::{
4 boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_REGTEST, 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 maybe_sync::{MaybeSend, MaybeSync};
15use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
16use rusqlite::ToSql;
17use sdk_common::bitcoin::hashes::hex::ToHex as _;
18use sdk_common::prelude::*;
19use sdk_common::utils::Arc;
20use serde::{Deserialize, Serialize};
21use std::cmp::PartialEq;
22use std::path::PathBuf;
23use std::str::FromStr;
24use strum_macros::{Display, EnumString};
25
26use crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT;
27use crate::utils;
28use crate::{
29 bitcoin,
30 chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
31 elements,
32 error::{PaymentError, SdkError, SdkResult},
33};
34use crate::{
35 chain::bitcoin::esplora::EsploraBitcoinChainService,
36 chain::liquid::esplora::EsploraLiquidChainService, prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
37};
38
39pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
41pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
42pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
43pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api";
44
45const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
46
47#[derive(Clone, Debug, Serialize)]
48pub enum BlockchainExplorer {
49 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
50 Electrum { url: String },
51 Esplora {
52 url: String,
53 use_waterfalls: bool,
55 },
56}
57
58#[derive(Clone, Debug, Serialize)]
60pub struct Config {
61 pub liquid_explorer: BlockchainExplorer,
62 pub bitcoin_explorer: BlockchainExplorer,
63 pub working_dir: String,
67 pub cache_dir: Option<String>,
69 pub network: LiquidNetwork,
70 pub payment_timeout_sec: u64,
72 pub sync_service_url: Option<String>,
75 pub zero_conf_max_amount_sat: Option<u64>,
78 pub breez_api_key: Option<String>,
80 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
84 pub use_default_external_input_parsers: bool,
88 pub onchain_fee_rate_leeway_sat_per_vbyte: Option<u32>,
95 pub asset_metadata: Option<Vec<AssetMetadata>>,
100 pub sideswap_api_key: Option<String>,
102}
103
104impl Config {
105 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
106 pub fn mainnet(breez_api_key: Option<String>) -> Self {
107 Config {
108 liquid_explorer: BlockchainExplorer::Electrum {
109 url: "elements-mainnet.breez.technology:50002".to_string(),
110 },
111 bitcoin_explorer: BlockchainExplorer::Electrum {
112 url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
113 },
114 working_dir: ".".to_string(),
115 cache_dir: None,
116 network: LiquidNetwork::Mainnet,
117 payment_timeout_sec: 15,
118 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
119 zero_conf_max_amount_sat: None,
120 breez_api_key,
121 external_input_parsers: None,
122 use_default_external_input_parsers: true,
123 onchain_fee_rate_leeway_sat_per_vbyte: None,
124 asset_metadata: None,
125 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
126 }
127 }
128
129 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
130 Config {
131 liquid_explorer: BlockchainExplorer::Esplora {
132 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
133 use_waterfalls: true,
134 },
135 bitcoin_explorer: BlockchainExplorer::Esplora {
136 url: "https://blockstream.info/api/".to_string(),
137 use_waterfalls: false,
138 },
139 working_dir: ".".to_string(),
140 cache_dir: None,
141 network: LiquidNetwork::Mainnet,
142 payment_timeout_sec: 15,
143 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
144 zero_conf_max_amount_sat: None,
145 breez_api_key,
146 external_input_parsers: None,
147 use_default_external_input_parsers: true,
148 onchain_fee_rate_leeway_sat_per_vbyte: None,
149 asset_metadata: None,
150 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
151 }
152 }
153
154 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
155 pub fn testnet(breez_api_key: Option<String>) -> Self {
156 Config {
157 liquid_explorer: BlockchainExplorer::Electrum {
158 url: "elements-testnet.blockstream.info:50002".to_string(),
159 },
160 bitcoin_explorer: BlockchainExplorer::Electrum {
161 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
162 },
163 working_dir: ".".to_string(),
164 cache_dir: None,
165 network: LiquidNetwork::Testnet,
166 payment_timeout_sec: 15,
167 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
168 zero_conf_max_amount_sat: None,
169 breez_api_key,
170 external_input_parsers: None,
171 use_default_external_input_parsers: true,
172 onchain_fee_rate_leeway_sat_per_vbyte: None,
173 asset_metadata: None,
174 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
175 }
176 }
177
178 pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
179 Config {
180 liquid_explorer: BlockchainExplorer::Esplora {
181 url: "https://blockstream.info/liquidtestnet/api".to_string(),
182 use_waterfalls: false,
183 },
184 bitcoin_explorer: BlockchainExplorer::Esplora {
185 url: "https://blockstream.info/testnet/api/".to_string(),
186 use_waterfalls: false,
187 },
188 working_dir: ".".to_string(),
189 cache_dir: None,
190 network: LiquidNetwork::Testnet,
191 payment_timeout_sec: 15,
192 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
193 zero_conf_max_amount_sat: None,
194 breez_api_key,
195 external_input_parsers: None,
196 use_default_external_input_parsers: true,
197 onchain_fee_rate_leeway_sat_per_vbyte: None,
198 asset_metadata: None,
199 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
200 }
201 }
202
203 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
204 pub fn regtest() -> Self {
205 Config {
206 liquid_explorer: BlockchainExplorer::Electrum {
207 url: "localhost:19002".to_string(),
208 },
209 bitcoin_explorer: BlockchainExplorer::Electrum {
210 url: "localhost:19001".to_string(),
211 },
212 working_dir: ".".to_string(),
213 cache_dir: None,
214 network: LiquidNetwork::Regtest,
215 payment_timeout_sec: 15,
216 sync_service_url: Some("http://localhost:8088".to_string()),
217 zero_conf_max_amount_sat: None,
218 breez_api_key: None,
219 external_input_parsers: None,
220 use_default_external_input_parsers: true,
221 onchain_fee_rate_leeway_sat_per_vbyte: None,
222 asset_metadata: None,
223 sideswap_api_key: None,
224 }
225 }
226
227 pub fn regtest_esplora() -> Self {
228 Config {
229 liquid_explorer: BlockchainExplorer::Esplora {
230 url: "http://localhost:4003/api".to_string(),
231 use_waterfalls: false,
232 },
233 bitcoin_explorer: BlockchainExplorer::Esplora {
234 url: "http://localhost:4002/api".to_string(),
235 use_waterfalls: false,
236 },
237 working_dir: ".".to_string(),
238 cache_dir: None,
239 network: LiquidNetwork::Regtest,
240 payment_timeout_sec: 15,
241 sync_service_url: Some("http://localhost:8088".to_string()),
242 zero_conf_max_amount_sat: None,
243 breez_api_key: None,
244 external_input_parsers: None,
245 use_default_external_input_parsers: true,
246 onchain_fee_rate_leeway_sat_per_vbyte: None,
247 asset_metadata: None,
248 sideswap_api_key: None,
249 }
250 }
251
252 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
253 Ok(PathBuf::from(base_dir)
254 .join(match self.network {
255 LiquidNetwork::Mainnet => "mainnet",
256 LiquidNetwork::Testnet => "testnet",
257 LiquidNetwork::Regtest => "regtest",
258 })
259 .join(fingerprint_hex)
260 .to_str()
261 .ok_or(anyhow::anyhow!(
262 "Could not get retrieve current wallet directory"
263 ))?
264 .to_string())
265 }
266
267 pub fn zero_conf_max_amount_sat(&self) -> u64 {
268 self.zero_conf_max_amount_sat
269 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
270 }
271
272 pub(crate) fn lbtc_asset_id(&self) -> String {
273 utils::lbtc_asset_id(self.network).to_string()
274 }
275
276 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
277 let mut external_input_parsers = Vec::new();
278 if self.use_default_external_input_parsers {
279 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
280 .iter()
281 .map(|(id, regex, url)| ExternalInputParser {
282 provider_id: id.to_string(),
283 input_regex: regex.to_string(),
284 parser_url: url.to_string(),
285 })
286 .collect::<Vec<_>>();
287 external_input_parsers.extend(default_parsers);
288 }
289 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
290
291 external_input_parsers
292 }
293
294 pub(crate) fn default_boltz_url(&self) -> &str {
295 match self.network {
296 LiquidNetwork::Mainnet => BOLTZ_MAINNET_URL_V2,
297 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
298 LiquidNetwork::Regtest => BOLTZ_REGTEST,
299 }
300 }
301
302 pub fn sync_enabled(&self) -> bool {
303 self.sync_service_url.is_some()
304 }
305
306 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
307 match self.bitcoin_explorer {
308 BlockchainExplorer::Esplora { .. } => {
309 Arc::new(EsploraBitcoinChainService::new(self.clone()))
310 }
311 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
312 BlockchainExplorer::Electrum { .. } => Arc::new(
313 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
314 ),
315 }
316 }
317
318 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
319 match &self.liquid_explorer {
320 BlockchainExplorer::Esplora { url, .. } => {
321 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
322 bail!("Cannot start the Breez Esplora chain service without providing a valid API key. See https://sdk-doc-liquid.breez.technology/guide/getting_started.html#api-key")
323 }
324 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
325 }
326 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
327 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
328 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
329 )),
330 }
331 }
332
333 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
334 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
335 match self.network {
336 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
337 LiquidNetwork::Regtest => (false, false),
338 }
339 }
340
341 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
342 pub(crate) fn electrum_client(
343 &self,
344 url: &str,
345 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
346 let (tls, validate_domain) = self.electrum_tls_options();
347 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
348 lwk_wollet::ElectrumClient::with_options(
349 &electrum_url,
350 lwk_wollet::ElectrumOptions { timeout: Some(3) },
351 )
352 }
353}
354
355#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
358pub enum LiquidNetwork {
359 Mainnet,
361 Testnet,
363 Regtest,
365}
366impl LiquidNetwork {
367 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
368 match self {
369 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
370 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
371 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
372 }
373 }
374}
375
376impl From<LiquidNetwork> for ElementsNetwork {
377 fn from(value: LiquidNetwork) -> Self {
378 match value {
379 LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
380 LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
381 LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
382 policy_asset: AssetId::from_str(
383 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
384 )
385 .unwrap(),
386 },
387 }
388 }
389}
390
391impl From<LiquidNetwork> for Chain {
392 fn from(value: LiquidNetwork) -> Self {
393 Chain::Liquid(value.into())
394 }
395}
396
397impl From<LiquidNetwork> for LiquidChain {
398 fn from(value: LiquidNetwork) -> Self {
399 match value {
400 LiquidNetwork::Mainnet => LiquidChain::Liquid,
401 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
402 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
403 }
404 }
405}
406
407impl TryFrom<&str> for LiquidNetwork {
408 type Error = anyhow::Error;
409
410 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
411 match value.to_lowercase().as_str() {
412 "mainnet" => Ok(LiquidNetwork::Mainnet),
413 "testnet" => Ok(LiquidNetwork::Testnet),
414 "regtest" => Ok(LiquidNetwork::Regtest),
415 _ => Err(anyhow!("Invalid network")),
416 }
417 }
418}
419
420impl From<LiquidNetwork> for Network {
421 fn from(value: LiquidNetwork) -> Self {
422 match value {
423 LiquidNetwork::Mainnet => Self::Bitcoin,
424 LiquidNetwork::Testnet => Self::Testnet,
425 LiquidNetwork::Regtest => Self::Regtest,
426 }
427 }
428}
429
430impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
431 fn from(value: LiquidNetwork) -> Self {
432 match value {
433 LiquidNetwork::Mainnet => Self::Bitcoin,
434 LiquidNetwork::Testnet => Self::Testnet,
435 LiquidNetwork::Regtest => Self::Regtest,
436 }
437 }
438}
439
440impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
441 fn from(value: LiquidNetwork) -> Self {
442 match value {
443 LiquidNetwork::Mainnet => Self::Bitcoin,
444 LiquidNetwork::Testnet => Self::Testnet,
445 LiquidNetwork::Regtest => Self::Regtest,
446 }
447 }
448}
449
450pub trait EventListener: MaybeSend + MaybeSync {
452 fn on_event(&self, e: SdkEvent);
453}
454
455#[derive(Clone, Debug, PartialEq)]
458pub enum SdkEvent {
459 PaymentFailed {
460 details: Payment,
461 },
462 PaymentPending {
463 details: Payment,
464 },
465 PaymentRefundable {
466 details: Payment,
467 },
468 PaymentRefunded {
469 details: Payment,
470 },
471 PaymentRefundPending {
472 details: Payment,
473 },
474 PaymentSucceeded {
475 details: Payment,
476 },
477 PaymentWaitingConfirmation {
478 details: Payment,
479 },
480 PaymentWaitingFeeAcceptance {
481 details: Payment,
482 },
483 Synced,
485 DataSynced {
487 did_pull_new_records: bool,
489 },
490}
491
492#[derive(thiserror::Error, Debug)]
493pub enum SignerError {
494 #[error("Signer error: {err}")]
495 Generic { err: String },
496}
497
498impl From<anyhow::Error> for SignerError {
499 fn from(err: anyhow::Error) -> Self {
500 SignerError::Generic {
501 err: err.to_string(),
502 }
503 }
504}
505
506impl From<bip32::Error> for SignerError {
507 fn from(err: bip32::Error) -> Self {
508 SignerError::Generic {
509 err: err.to_string(),
510 }
511 }
512}
513
514pub trait Signer: MaybeSend + MaybeSync {
517 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
520
521 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
527
528 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
530
531 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
533
534 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
536
537 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
540
541 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
543
544 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
546}
547
548pub struct ConnectRequest {
551 pub config: Config,
553 pub mnemonic: Option<String>,
555 pub passphrase: Option<String>,
557 pub seed: Option<Vec<u8>>,
559}
560
561pub struct ConnectWithSignerRequest {
562 pub config: Config,
563}
564
565#[derive(Clone, Debug)]
568pub(crate) struct ReservedAddress {
569 pub(crate) address: String,
571 pub(crate) expiry_block_height: u32,
573}
574
575#[derive(Clone, Debug, EnumString, Serialize, Eq, PartialEq)]
577pub enum PaymentMethod {
578 #[strum(serialize = "lightning")]
579 Lightning,
580 #[strum(serialize = "bitcoin")]
581 BitcoinAddress,
582 #[strum(serialize = "liquid")]
583 LiquidAddress,
584}
585
586#[derive(Debug, Serialize, Clone)]
587pub enum ReceiveAmount {
588 Bitcoin { payer_amount_sat: u64 },
590
591 Asset {
593 asset_id: String,
594 payer_amount: Option<f64>,
595 },
596}
597
598#[derive(Debug, Serialize)]
600pub struct PrepareReceiveRequest {
601 pub payment_method: PaymentMethod,
602
603 pub amount: Option<ReceiveAmount>,
605}
606
607#[derive(Debug, Serialize, Clone)]
609pub struct PrepareReceiveResponse {
610 pub payment_method: PaymentMethod,
611 pub amount: Option<ReceiveAmount>,
612
613 pub fees_sat: u64,
622
623 pub min_payer_amount_sat: Option<u64>,
627
628 pub max_payer_amount_sat: Option<u64>,
632
633 pub swapper_feerate: Option<f64>,
637}
638
639#[derive(Debug, Serialize)]
641pub struct ReceivePaymentRequest {
642 pub prepare_response: PrepareReceiveResponse,
643 pub description: Option<String>,
645 pub use_description_hash: Option<bool>,
647}
648
649#[derive(Debug, Serialize)]
651pub struct ReceivePaymentResponse {
652 pub destination: String,
655}
656
657#[derive(Debug, Serialize)]
659pub struct Limits {
660 pub min_sat: u64,
661 pub max_sat: u64,
662 pub max_zero_conf_sat: u64,
663}
664
665#[derive(Debug, Serialize)]
667pub struct LightningPaymentLimitsResponse {
668 pub send: Limits,
670 pub receive: Limits,
672}
673
674#[derive(Debug, Serialize)]
676pub struct OnchainPaymentLimitsResponse {
677 pub send: Limits,
679 pub receive: Limits,
681}
682
683#[derive(Debug, Serialize, Clone)]
685pub struct PrepareSendRequest {
686 pub destination: String,
689
690 pub amount: Option<PayAmount>,
693}
694
695#[derive(Clone, Debug, Serialize)]
697pub enum SendDestination {
698 LiquidAddress {
699 address_data: liquid::LiquidAddressData,
700 bip353_address: Option<String>,
702 },
703 Bolt11 {
704 invoice: LNInvoice,
705 bip353_address: Option<String>,
707 },
708 Bolt12 {
709 offer: LNOffer,
710 receiver_amount_sat: u64,
711 bip353_address: Option<String>,
713 },
714}
715
716#[derive(Debug, Serialize, Clone)]
718pub struct PrepareSendResponse {
719 pub destination: SendDestination,
720 pub fees_sat: Option<u64>,
723 pub estimated_asset_fees: Option<f64>,
727}
728
729#[derive(Debug, Serialize)]
731pub struct SendPaymentRequest {
732 pub prepare_response: PrepareSendResponse,
733 pub use_asset_fees: Option<bool>,
734}
735
736#[derive(Debug, Serialize)]
738pub struct SendPaymentResponse {
739 pub payment: Payment,
740}
741
742#[derive(Debug, Serialize, Clone)]
744pub enum PayAmount {
745 Bitcoin { receiver_amount_sat: u64 },
747
748 Asset {
750 asset_id: String,
751 receiver_amount: f64,
752 estimate_asset_fees: Option<bool>,
753 },
754
755 Drain,
757}
758
759#[derive(Debug, Serialize, Clone)]
761pub struct PreparePayOnchainRequest {
762 pub amount: PayAmount,
764 pub fee_rate_sat_per_vbyte: Option<u32>,
766}
767
768#[derive(Debug, Serialize, Clone)]
770pub struct PreparePayOnchainResponse {
771 pub receiver_amount_sat: u64,
772 pub claim_fees_sat: u64,
773 pub total_fees_sat: u64,
774}
775
776#[derive(Debug, Serialize)]
778pub struct PayOnchainRequest {
779 pub address: String,
780 pub prepare_response: PreparePayOnchainResponse,
781}
782
783#[derive(Debug, Serialize)]
785pub struct PrepareRefundRequest {
786 pub swap_address: String,
788 pub refund_address: String,
790 pub fee_rate_sat_per_vbyte: u32,
792}
793
794#[derive(Debug, Serialize)]
796pub struct PrepareRefundResponse {
797 pub tx_vsize: u32,
798 pub tx_fee_sat: u64,
799 pub last_refund_tx_id: Option<String>,
801}
802
803#[derive(Debug, Serialize)]
805pub struct RefundRequest {
806 pub swap_address: String,
808 pub refund_address: String,
810 pub fee_rate_sat_per_vbyte: u32,
812}
813
814#[derive(Debug, Serialize)]
816pub struct RefundResponse {
817 pub refund_tx_id: String,
818}
819
820#[derive(Clone, Debug, Default, Serialize, Deserialize)]
822pub struct AssetBalance {
823 pub asset_id: String,
824 pub balance_sat: u64,
825 pub name: Option<String>,
826 pub ticker: Option<String>,
827 pub balance: Option<f64>,
828}
829
830#[derive(Debug, Serialize, Deserialize, Default)]
831pub struct BlockchainInfo {
832 pub liquid_tip: u32,
833 pub bitcoin_tip: u32,
834}
835
836#[derive(Debug, Serialize, Deserialize)]
837pub struct WalletInfo {
838 pub balance_sat: u64,
840 pub pending_send_sat: u64,
842 pub pending_receive_sat: u64,
844 pub fingerprint: String,
846 pub pubkey: String,
848 #[serde(default)]
850 pub asset_balances: Vec<AssetBalance>,
851}
852
853impl WalletInfo {
854 pub(crate) fn validate_sufficient_funds(
855 &self,
856 network: LiquidNetwork,
857 amount_sat: u64,
858 fees_sat: Option<u64>,
859 asset_id: &str,
860 ) -> Result<(), PaymentError> {
861 let fees_sat = fees_sat.unwrap_or(0);
862 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
863 ensure_sdk!(
864 amount_sat + fees_sat <= self.balance_sat,
865 PaymentError::InsufficientFunds
866 );
867 } else {
868 match self
869 .asset_balances
870 .iter()
871 .find(|ab| ab.asset_id.eq(asset_id))
872 {
873 Some(asset_balance) => ensure_sdk!(
874 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
875 PaymentError::InsufficientFunds
876 ),
877 None => return Err(PaymentError::InsufficientFunds),
878 }
879 }
880 Ok(())
881 }
882}
883
884#[derive(Debug, Serialize, Deserialize)]
886pub struct GetInfoResponse {
887 pub wallet_info: WalletInfo,
889 #[serde(default)]
891 pub blockchain_info: BlockchainInfo,
892}
893
894#[derive(Clone, Debug, PartialEq)]
896pub struct SignMessageRequest {
897 pub message: String,
898}
899
900#[derive(Clone, Debug, PartialEq)]
902pub struct SignMessageResponse {
903 pub signature: String,
904}
905
906#[derive(Clone, Debug, PartialEq)]
908pub struct CheckMessageRequest {
909 pub message: String,
911 pub pubkey: String,
913 pub signature: String,
915}
916
917#[derive(Clone, Debug, PartialEq)]
919pub struct CheckMessageResponse {
920 pub is_valid: bool,
923}
924
925#[derive(Debug, Serialize)]
927pub struct BackupRequest {
928 pub backup_path: Option<String>,
935}
936
937#[derive(Debug, Serialize)]
939pub struct RestoreRequest {
940 pub backup_path: Option<String>,
941}
942
943#[derive(Default)]
945pub struct ListPaymentsRequest {
946 pub filters: Option<Vec<PaymentType>>,
947 pub states: Option<Vec<PaymentState>>,
948 pub from_timestamp: Option<i64>,
950 pub to_timestamp: Option<i64>,
952 pub offset: Option<u32>,
953 pub limit: Option<u32>,
954 pub details: Option<ListPaymentDetails>,
955 pub sort_ascending: Option<bool>,
956}
957
958#[derive(Debug, Serialize)]
960pub enum ListPaymentDetails {
961 Liquid {
963 asset_id: Option<String>,
965 destination: Option<String>,
967 },
968
969 Bitcoin {
971 address: Option<String>,
973 },
974}
975
976#[derive(Debug, Serialize)]
978pub enum GetPaymentRequest {
979 PaymentHash { payment_hash: String },
981 SwapId { swap_id: String },
983}
984
985#[sdk_macros::async_trait]
987pub(crate) trait BlockListener: MaybeSend + MaybeSync {
988 async fn on_bitcoin_block(&self, height: u32);
989 async fn on_liquid_block(&self, height: u32);
990}
991
992#[derive(Clone, Debug)]
994pub enum Swap {
995 Chain(ChainSwap),
996 Send(SendSwap),
997 Receive(ReceiveSwap),
998}
999impl Swap {
1000 pub(crate) fn id(&self) -> String {
1001 match &self {
1002 Swap::Chain(ChainSwap { id, .. })
1003 | Swap::Send(SendSwap { id, .. })
1004 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1005 }
1006 }
1007
1008 pub(crate) fn version(&self) -> u64 {
1009 match self {
1010 Swap::Chain(ChainSwap { metadata, .. })
1011 | Swap::Send(SendSwap { metadata, .. })
1012 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1013 }
1014 }
1015
1016 pub(crate) fn set_version(&mut self, version: u64) {
1017 match self {
1018 Swap::Chain(chain_swap) => {
1019 chain_swap.metadata.version = version;
1020 }
1021 Swap::Send(send_swap) => {
1022 send_swap.metadata.version = version;
1023 }
1024 Swap::Receive(receive_swap) => {
1025 receive_swap.metadata.version = version;
1026 }
1027 }
1028 }
1029
1030 pub(crate) fn is_local(&self) -> bool {
1031 match self {
1032 Swap::Chain(ChainSwap { metadata, .. })
1033 | Swap::Send(SendSwap { metadata, .. })
1034 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.is_local,
1035 }
1036 }
1037
1038 pub(crate) fn last_updated_at(&self) -> u32 {
1039 match self {
1040 Swap::Chain(ChainSwap { metadata, .. })
1041 | Swap::Send(SendSwap { metadata, .. })
1042 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1043 }
1044 }
1045}
1046impl From<ChainSwap> for Swap {
1047 fn from(swap: ChainSwap) -> Self {
1048 Self::Chain(swap)
1049 }
1050}
1051impl From<SendSwap> for Swap {
1052 fn from(swap: SendSwap) -> Self {
1053 Self::Send(swap)
1054 }
1055}
1056impl From<ReceiveSwap> for Swap {
1057 fn from(swap: ReceiveSwap) -> Self {
1058 Self::Receive(swap)
1059 }
1060}
1061
1062#[derive(Clone, Debug)]
1063pub(crate) enum SwapScriptV2 {
1064 Bitcoin(BtcSwapScript),
1065 Liquid(LBtcSwapScript),
1066}
1067impl SwapScriptV2 {
1068 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1069 match self {
1070 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1071 _ => Err(anyhow!("Invalid chain")),
1072 }
1073 }
1074
1075 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1076 match self {
1077 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1078 _ => Err(anyhow!("Invalid chain")),
1079 }
1080 }
1081}
1082
1083#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1084pub enum Direction {
1085 Incoming = 0,
1086 Outgoing = 1,
1087}
1088impl ToSql for Direction {
1089 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1090 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1091 }
1092}
1093impl FromSql for Direction {
1094 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1095 match value {
1096 ValueRef::Integer(i) => match i as u8 {
1097 0 => Ok(Direction::Incoming),
1098 1 => Ok(Direction::Outgoing),
1099 _ => Err(FromSqlError::OutOfRange(i)),
1100 },
1101 _ => Err(FromSqlError::InvalidType),
1102 }
1103 }
1104}
1105
1106#[derive(Clone, Debug, Default)]
1107pub(crate) struct SwapMetadata {
1108 pub(crate) version: u64,
1110 pub(crate) last_updated_at: u32,
1111 pub(crate) is_local: bool,
1112}
1113
1114#[derive(Clone, Debug, Derivative)]
1118#[derivative(PartialEq)]
1119pub struct ChainSwap {
1120 pub(crate) id: String,
1121 pub(crate) direction: Direction,
1122 pub(crate) claim_address: Option<String>,
1124 pub(crate) lockup_address: String,
1125 pub(crate) refund_address: Option<String>,
1127 pub(crate) timeout_block_height: u32,
1128 pub(crate) preimage: String,
1129 pub(crate) description: Option<String>,
1130 pub(crate) payer_amount_sat: u64,
1132 pub(crate) actual_payer_amount_sat: Option<u64>,
1135 pub(crate) receiver_amount_sat: u64,
1137 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1139 pub(crate) claim_fees_sat: u64,
1140 pub(crate) pair_fees_json: String,
1142 pub(crate) accept_zero_conf: bool,
1143 pub(crate) create_response_json: String,
1145 pub(crate) server_lockup_tx_id: Option<String>,
1147 pub(crate) user_lockup_tx_id: Option<String>,
1149 pub(crate) claim_tx_id: Option<String>,
1151 pub(crate) refund_tx_id: Option<String>,
1153 pub(crate) created_at: u32,
1154 pub(crate) state: PaymentState,
1155 pub(crate) claim_private_key: String,
1156 pub(crate) refund_private_key: String,
1157 pub(crate) auto_accepted_fees: bool,
1158 #[derivative(PartialEq = "ignore")]
1160 pub(crate) metadata: SwapMetadata,
1161}
1162impl ChainSwap {
1163 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1164 utils::decode_keypair(&self.claim_private_key)
1165 }
1166
1167 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1168 utils::decode_keypair(&self.refund_private_key)
1169 }
1170
1171 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1172 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1173 serde_json::from_str(&self.create_response_json).map_err(|e| {
1174 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1175 })?;
1176
1177 Ok(CreateChainResponse {
1178 id: self.id.clone(),
1179 claim_details: internal_create_response.claim_details,
1180 lockup_details: internal_create_response.lockup_details,
1181 })
1182 }
1183
1184 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1185 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1186 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1187
1188 Ok(pair)
1189 }
1190
1191 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1192 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1193 let our_pubkey = self.get_claim_keypair()?.public_key();
1194 let swap_script = match self.direction {
1195 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1196 Side::Claim,
1197 chain_swap_details,
1198 our_pubkey.into(),
1199 )?),
1200 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1201 Side::Claim,
1202 chain_swap_details,
1203 our_pubkey.into(),
1204 )?),
1205 };
1206 Ok(swap_script)
1207 }
1208
1209 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1210 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1211 let our_pubkey = self.get_refund_keypair()?.public_key();
1212 let swap_script = match self.direction {
1213 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1214 Side::Lockup,
1215 chain_swap_details,
1216 our_pubkey.into(),
1217 )?),
1218 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1219 Side::Lockup,
1220 chain_swap_details,
1221 our_pubkey.into(),
1222 )?),
1223 };
1224 Ok(swap_script)
1225 }
1226
1227 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1229 &self,
1230 network: LiquidNetwork,
1231 ) -> SdkResult<ScriptBuf> {
1232 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1233 let script_pubkey = swap_script
1234 .to_address(network.as_bitcoin_chain())
1235 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1236 .script_pubkey();
1237 Ok(script_pubkey)
1238 }
1239
1240 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1241 RefundableSwap {
1242 swap_address: self.lockup_address.clone(),
1243 timestamp: self.created_at,
1244 amount_sat,
1245 last_refund_tx_id: self.refund_tx_id.clone(),
1246 }
1247 }
1248
1249 pub(crate) fn from_boltz_struct_to_json(
1250 create_response: &CreateChainResponse,
1251 expected_swap_id: &str,
1252 ) -> Result<String, PaymentError> {
1253 let internal_create_response =
1254 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1255 create_response,
1256 expected_swap_id,
1257 )?;
1258
1259 let create_response_json =
1260 serde_json::to_string(&internal_create_response).map_err(|e| {
1261 PaymentError::Generic {
1262 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1263 }
1264 })?;
1265
1266 Ok(create_response_json)
1267 }
1268
1269 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1270 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1271 }
1272}
1273
1274#[derive(Clone, Debug, Default)]
1275pub(crate) struct ChainSwapUpdate {
1276 pub(crate) swap_id: String,
1277 pub(crate) to_state: PaymentState,
1278 pub(crate) server_lockup_tx_id: Option<String>,
1279 pub(crate) user_lockup_tx_id: Option<String>,
1280 pub(crate) claim_address: Option<String>,
1281 pub(crate) claim_tx_id: Option<String>,
1282 pub(crate) refund_tx_id: Option<String>,
1283}
1284
1285#[derive(Clone, Debug, Derivative)]
1287#[derivative(PartialEq)]
1288pub struct SendSwap {
1289 pub(crate) id: String,
1290 pub(crate) invoice: String,
1292 pub(crate) bolt12_offer: Option<String>,
1294 pub(crate) payment_hash: Option<String>,
1295 pub(crate) destination_pubkey: Option<String>,
1296 pub(crate) description: Option<String>,
1297 pub(crate) preimage: Option<String>,
1298 pub(crate) payer_amount_sat: u64,
1299 pub(crate) receiver_amount_sat: u64,
1300 pub(crate) pair_fees_json: String,
1302 pub(crate) create_response_json: String,
1304 pub(crate) lockup_tx_id: Option<String>,
1306 pub(crate) refund_address: Option<String>,
1308 pub(crate) refund_tx_id: Option<String>,
1310 pub(crate) created_at: u32,
1311 pub(crate) timeout_block_height: u64,
1312 pub(crate) state: PaymentState,
1313 pub(crate) refund_private_key: String,
1314 #[derivative(PartialEq = "ignore")]
1316 pub(crate) metadata: SwapMetadata,
1317}
1318impl SendSwap {
1319 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1320 utils::decode_keypair(&self.refund_private_key)
1321 }
1322
1323 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1324 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1325 serde_json::from_str(&self.create_response_json).map_err(|e| {
1326 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1327 })?;
1328
1329 let res = CreateSubmarineResponse {
1330 id: self.id.clone(),
1331 accept_zero_conf: internal_create_response.accept_zero_conf,
1332 address: internal_create_response.address.clone(),
1333 bip21: internal_create_response.bip21.clone(),
1334 claim_public_key: crate::utils::json_to_pubkey(
1335 &internal_create_response.claim_public_key,
1336 )?,
1337 expected_amount: internal_create_response.expected_amount,
1338 referral_id: internal_create_response.referral_id,
1339 swap_tree: internal_create_response.swap_tree.clone().into(),
1340 timeout_block_height: internal_create_response.timeout_block_height,
1341 blinding_key: internal_create_response.blinding_key.clone(),
1342 };
1343 Ok(res)
1344 }
1345
1346 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1347 LBtcSwapScript::submarine_from_swap_resp(
1348 &self.get_boltz_create_response()?,
1349 self.get_refund_keypair()?.public_key().into(),
1350 )
1351 .map_err(|e| {
1352 SdkError::generic(format!(
1353 "Failed to create swap script for Send Swap {}: {e:?}",
1354 self.id
1355 ))
1356 })
1357 }
1358
1359 pub(crate) fn from_boltz_struct_to_json(
1360 create_response: &CreateSubmarineResponse,
1361 expected_swap_id: &str,
1362 ) -> Result<String, PaymentError> {
1363 let internal_create_response =
1364 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1365 create_response,
1366 expected_swap_id,
1367 )?;
1368
1369 let create_response_json =
1370 serde_json::to_string(&internal_create_response).map_err(|e| {
1371 PaymentError::Generic {
1372 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1373 }
1374 })?;
1375
1376 Ok(create_response_json)
1377 }
1378}
1379
1380#[derive(Clone, Debug, Derivative)]
1382#[derivative(PartialEq)]
1383pub struct ReceiveSwap {
1384 pub(crate) id: String,
1385 pub(crate) preimage: String,
1386 pub(crate) create_response_json: String,
1388 pub(crate) claim_private_key: String,
1389 pub(crate) invoice: String,
1390 pub(crate) payment_hash: Option<String>,
1391 pub(crate) destination_pubkey: Option<String>,
1392 pub(crate) description: Option<String>,
1393 pub(crate) payer_amount_sat: u64,
1395 pub(crate) receiver_amount_sat: u64,
1396 pub(crate) pair_fees_json: String,
1398 pub(crate) claim_fees_sat: u64,
1399 pub(crate) claim_address: Option<String>,
1401 pub(crate) claim_tx_id: Option<String>,
1403 pub(crate) lockup_tx_id: Option<String>,
1405 pub(crate) mrh_address: String,
1407 pub(crate) mrh_tx_id: Option<String>,
1409 pub(crate) created_at: u32,
1412 pub(crate) timeout_block_height: u32,
1413 pub(crate) state: PaymentState,
1414 #[derivative(PartialEq = "ignore")]
1416 pub(crate) metadata: SwapMetadata,
1417}
1418impl ReceiveSwap {
1419 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1420 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1421 }
1422
1423 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1424 Ok(self
1425 .get_swap_script()?
1426 .funding_addrs
1427 .ok_or(anyhow!("No funding address found"))?
1428 .script_pubkey())
1429 }
1430
1431 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1432 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1433 serde_json::from_str(&self.create_response_json).map_err(|e| {
1434 PaymentError::Generic {
1435 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1436 }
1437 })?;
1438
1439 let res = CreateReverseResponse {
1440 id: self.id.clone(),
1441 invoice: self.invoice.clone(),
1442 swap_tree: internal_create_response.swap_tree.clone().into(),
1443 lockup_address: internal_create_response.lockup_address.clone(),
1444 refund_public_key: crate::utils::json_to_pubkey(
1445 &internal_create_response.refund_public_key,
1446 )?,
1447 timeout_block_height: internal_create_response.timeout_block_height,
1448 onchain_amount: internal_create_response.onchain_amount,
1449 blinding_key: internal_create_response.blinding_key.clone(),
1450 };
1451 Ok(res)
1452 }
1453
1454 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1455 let keypair = self.get_claim_keypair()?;
1456 let create_response =
1457 self.get_boltz_create_response()
1458 .map_err(|e| PaymentError::Generic {
1459 err: format!(
1460 "Failed to create swap script for Receive Swap {}: {e:?}",
1461 self.id
1462 ),
1463 })?;
1464 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1465 .map_err(|e| PaymentError::Generic {
1466 err: format!(
1467 "Failed to create swap script for Receive Swap {}: {e:?}",
1468 self.id
1469 ),
1470 })
1471 }
1472
1473 pub(crate) fn from_boltz_struct_to_json(
1474 create_response: &CreateReverseResponse,
1475 expected_swap_id: &str,
1476 expected_invoice: &str,
1477 ) -> Result<String, PaymentError> {
1478 let internal_create_response =
1479 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1480 create_response,
1481 expected_swap_id,
1482 expected_invoice,
1483 )?;
1484
1485 let create_response_json =
1486 serde_json::to_string(&internal_create_response).map_err(|e| {
1487 PaymentError::Generic {
1488 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1489 }
1490 })?;
1491
1492 Ok(create_response_json)
1493 }
1494}
1495
1496#[derive(Clone, Debug, PartialEq, Serialize)]
1498pub struct RefundableSwap {
1499 pub swap_address: String,
1500 pub timestamp: u32,
1501 pub amount_sat: u64,
1503 pub last_refund_tx_id: Option<String>,
1505}
1506
1507#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1509#[strum(serialize_all = "lowercase")]
1510pub enum PaymentState {
1511 #[default]
1512 Created = 0,
1513
1514 Pending = 1,
1534
1535 Complete = 2,
1547
1548 Failed = 3,
1556
1557 TimedOut = 4,
1562
1563 Refundable = 5,
1568
1569 RefundPending = 6,
1575
1576 WaitingFeeAcceptance = 7,
1588}
1589
1590impl ToSql for PaymentState {
1591 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1592 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1593 }
1594}
1595impl FromSql for PaymentState {
1596 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1597 match value {
1598 ValueRef::Integer(i) => match i as u8 {
1599 0 => Ok(PaymentState::Created),
1600 1 => Ok(PaymentState::Pending),
1601 2 => Ok(PaymentState::Complete),
1602 3 => Ok(PaymentState::Failed),
1603 4 => Ok(PaymentState::TimedOut),
1604 5 => Ok(PaymentState::Refundable),
1605 6 => Ok(PaymentState::RefundPending),
1606 7 => Ok(PaymentState::WaitingFeeAcceptance),
1607 _ => Err(FromSqlError::OutOfRange(i)),
1608 },
1609 _ => Err(FromSqlError::InvalidType),
1610 }
1611 }
1612}
1613
1614impl PaymentState {
1615 pub(crate) fn is_refundable(&self) -> bool {
1616 matches!(
1617 self,
1618 PaymentState::Refundable
1619 | PaymentState::RefundPending
1620 | PaymentState::WaitingFeeAcceptance
1621 )
1622 }
1623}
1624
1625#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1626#[strum(serialize_all = "lowercase")]
1627pub enum PaymentType {
1628 Receive = 0,
1629 Send = 1,
1630}
1631impl From<Direction> for PaymentType {
1632 fn from(value: Direction) -> Self {
1633 match value {
1634 Direction::Incoming => Self::Receive,
1635 Direction::Outgoing => Self::Send,
1636 }
1637 }
1638}
1639impl ToSql for PaymentType {
1640 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1641 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1642 }
1643}
1644impl FromSql for PaymentType {
1645 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1646 match value {
1647 ValueRef::Integer(i) => match i as u8 {
1648 0 => Ok(PaymentType::Receive),
1649 1 => Ok(PaymentType::Send),
1650 _ => Err(FromSqlError::OutOfRange(i)),
1651 },
1652 _ => Err(FromSqlError::InvalidType),
1653 }
1654 }
1655}
1656
1657#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1658pub enum PaymentStatus {
1659 Pending = 0,
1660 Complete = 1,
1661}
1662impl ToSql for PaymentStatus {
1663 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1664 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1665 }
1666}
1667impl FromSql for PaymentStatus {
1668 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1669 match value {
1670 ValueRef::Integer(i) => match i as u8 {
1671 0 => Ok(PaymentStatus::Pending),
1672 1 => Ok(PaymentStatus::Complete),
1673 _ => Err(FromSqlError::OutOfRange(i)),
1674 },
1675 _ => Err(FromSqlError::InvalidType),
1676 }
1677 }
1678}
1679
1680#[derive(Debug, Clone, Serialize)]
1681pub struct PaymentTxData {
1682 pub tx_id: String,
1684
1685 pub timestamp: Option<u32>,
1687
1688 pub asset_id: String,
1690
1691 pub amount: u64,
1695
1696 pub fees_sat: u64,
1698
1699 pub payment_type: PaymentType,
1700
1701 pub is_confirmed: bool,
1703
1704 pub unblinding_data: Option<String>,
1707}
1708
1709#[derive(Debug, Clone, Serialize)]
1710pub enum PaymentSwapType {
1711 Receive,
1712 Send,
1713 Chain,
1714}
1715
1716#[derive(Debug, Clone, Serialize)]
1717pub struct PaymentSwapData {
1718 pub swap_id: String,
1719
1720 pub swap_type: PaymentSwapType,
1721
1722 pub created_at: u32,
1724
1725 pub expiration_blockheight: u32,
1727
1728 pub preimage: Option<String>,
1729 pub invoice: Option<String>,
1730 pub bolt12_offer: Option<String>,
1731 pub payment_hash: Option<String>,
1732 pub destination_pubkey: Option<String>,
1733 pub description: String,
1734
1735 pub payer_amount_sat: u64,
1737
1738 pub receiver_amount_sat: u64,
1740
1741 pub swapper_fees_sat: u64,
1743
1744 pub refund_tx_id: Option<String>,
1745 pub refund_tx_amount_sat: Option<u64>,
1746
1747 pub claim_address: Option<String>,
1751
1752 pub status: PaymentState,
1754}
1755
1756#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1758pub struct LnUrlInfo {
1759 pub ln_address: Option<String>,
1760 pub lnurl_pay_comment: Option<String>,
1761 pub lnurl_pay_domain: Option<String>,
1762 pub lnurl_pay_metadata: Option<String>,
1763 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1764 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1765 pub lnurl_withdraw_endpoint: Option<String>,
1766}
1767
1768#[derive(Debug, Clone, Serialize)]
1772pub struct AssetMetadata {
1773 pub asset_id: String,
1775 pub name: String,
1777 pub ticker: String,
1779 pub precision: u8,
1782 pub fiat_id: Option<String>,
1784}
1785
1786impl AssetMetadata {
1787 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1788 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1789 }
1790
1791 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1792 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1793 }
1794}
1795
1796#[derive(Clone, Debug, PartialEq, Serialize)]
1799pub struct AssetInfo {
1800 pub name: String,
1802 pub ticker: String,
1804 pub amount: f64,
1807 pub fees: Option<f64>,
1810}
1811
1812#[derive(Debug, Clone, PartialEq, Serialize)]
1814#[allow(clippy::large_enum_variant)]
1815pub enum PaymentDetails {
1816 Lightning {
1818 swap_id: String,
1819
1820 description: String,
1822
1823 liquid_expiration_blockheight: u32,
1825
1826 preimage: Option<String>,
1828
1829 invoice: Option<String>,
1833
1834 bolt12_offer: Option<String>,
1835
1836 payment_hash: Option<String>,
1838
1839 destination_pubkey: Option<String>,
1841
1842 lnurl_info: Option<LnUrlInfo>,
1844
1845 bip353_address: Option<String>,
1847
1848 claim_tx_id: Option<String>,
1850
1851 refund_tx_id: Option<String>,
1853
1854 refund_tx_amount_sat: Option<u64>,
1856 },
1857 Liquid {
1859 destination: String,
1861
1862 description: String,
1864
1865 asset_id: String,
1867
1868 asset_info: Option<AssetInfo>,
1870
1871 lnurl_info: Option<LnUrlInfo>,
1873
1874 bip353_address: Option<String>,
1876 },
1877 Bitcoin {
1879 swap_id: String,
1880
1881 description: String,
1883
1884 auto_accepted_fees: bool,
1888
1889 liquid_expiration_blockheight: Option<u32>,
1892
1893 bitcoin_expiration_blockheight: Option<u32>,
1896
1897 claim_tx_id: Option<String>,
1899
1900 refund_tx_id: Option<String>,
1902
1903 refund_tx_amount_sat: Option<u64>,
1905 },
1906}
1907
1908impl PaymentDetails {
1909 pub(crate) fn get_swap_id(&self) -> Option<String> {
1910 match self {
1911 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
1912 Some(swap_id.clone())
1913 }
1914 Self::Liquid { .. } => None,
1915 }
1916 }
1917
1918 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
1919 match self {
1920 Self::Lightning {
1921 refund_tx_amount_sat,
1922 ..
1923 }
1924 | Self::Bitcoin {
1925 refund_tx_amount_sat,
1926 ..
1927 } => *refund_tx_amount_sat,
1928 Self::Liquid { .. } => None,
1929 }
1930 }
1931
1932 pub(crate) fn get_description(&self) -> Option<String> {
1933 match self {
1934 Self::Lightning { description, .. }
1935 | Self::Bitcoin { description, .. }
1936 | Self::Liquid { description, .. } => Some(description.clone()),
1937 }
1938 }
1939
1940 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
1941 match self {
1942 Self::Liquid { asset_id, .. } => {
1943 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
1944 }
1945 _ => true,
1946 }
1947 }
1948}
1949
1950#[derive(Debug, Clone, PartialEq, Serialize)]
1954pub struct Payment {
1955 pub destination: Option<String>,
1958
1959 pub tx_id: Option<String>,
1960
1961 pub unblinding_data: Option<String>,
1964
1965 pub timestamp: u32,
1971
1972 pub amount_sat: u64,
1976
1977 pub fees_sat: u64,
1991
1992 pub swapper_fees_sat: Option<u64>,
1995
1996 pub payment_type: PaymentType,
1998
1999 pub status: PaymentState,
2005
2006 pub details: PaymentDetails,
2009}
2010impl Payment {
2011 pub(crate) fn from_pending_swap(
2012 swap: PaymentSwapData,
2013 payment_type: PaymentType,
2014 payment_details: PaymentDetails,
2015 ) -> Payment {
2016 let amount_sat = match payment_type {
2017 PaymentType::Receive => swap.receiver_amount_sat,
2018 PaymentType::Send => swap.payer_amount_sat,
2019 };
2020
2021 Payment {
2022 destination: swap.invoice.clone(),
2023 tx_id: None,
2024 unblinding_data: None,
2025 timestamp: swap.created_at,
2026 amount_sat,
2027 fees_sat: swap
2028 .payer_amount_sat
2029 .saturating_sub(swap.receiver_amount_sat),
2030 swapper_fees_sat: Some(swap.swapper_fees_sat),
2031 payment_type,
2032 status: swap.status,
2033 details: payment_details,
2034 }
2035 }
2036
2037 pub(crate) fn from_tx_data(
2038 tx: PaymentTxData,
2039 swap: Option<PaymentSwapData>,
2040 details: PaymentDetails,
2041 ) -> Payment {
2042 let (amount_sat, fees_sat) = match swap.as_ref() {
2043 Some(s) => match tx.payment_type {
2044 PaymentType::Receive => (tx.amount, s.payer_amount_sat.saturating_sub(tx.amount)),
2048 PaymentType::Send => (
2049 s.receiver_amount_sat,
2050 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2051 ),
2052 },
2053 None => {
2054 let (amount_sat, fees_sat) = match tx.payment_type {
2055 PaymentType::Receive => (tx.amount, 0),
2056 PaymentType::Send => (tx.amount, tx.fees_sat),
2057 };
2058 match details {
2061 PaymentDetails::Liquid {
2062 asset_info: Some(ref asset_info),
2063 ..
2064 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2065 _ => (amount_sat, fees_sat),
2066 }
2067 }
2068 };
2069 Payment {
2070 tx_id: Some(tx.tx_id),
2071 unblinding_data: tx.unblinding_data,
2072 destination: match &swap {
2076 Some(PaymentSwapData {
2077 swap_type: PaymentSwapType::Receive,
2078 invoice,
2079 ..
2080 }) => invoice.clone(),
2081 Some(PaymentSwapData {
2082 swap_type: PaymentSwapType::Send,
2083 invoice,
2084 bolt12_offer,
2085 ..
2086 }) => bolt12_offer.clone().or(invoice.clone()),
2087 Some(PaymentSwapData {
2088 swap_type: PaymentSwapType::Chain,
2089 claim_address,
2090 ..
2091 }) => claim_address.clone(),
2092 _ => match &details {
2093 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2094 _ => None,
2095 },
2096 },
2097 timestamp: tx
2098 .timestamp
2099 .or(swap.as_ref().map(|s| s.created_at))
2100 .unwrap_or(utils::now()),
2101 amount_sat,
2102 fees_sat,
2103 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2104 payment_type: tx.payment_type,
2105 status: match &swap {
2106 Some(swap) => swap.status,
2107 None => match tx.is_confirmed {
2108 true => PaymentState::Complete,
2109 false => PaymentState::Pending,
2110 },
2111 },
2112 details,
2113 }
2114 }
2115
2116 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2117 match self.details.clone() {
2118 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2119 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2120 PaymentDetails::Liquid { .. } => None,
2121 }
2122 .flatten()
2123 }
2124}
2125
2126#[derive(Deserialize, Serialize, Clone, Debug)]
2128#[serde(rename_all = "camelCase")]
2129pub struct RecommendedFees {
2130 pub fastest_fee: u64,
2131 pub half_hour_fee: u64,
2132 pub hour_fee: u64,
2133 pub economy_fee: u64,
2134 pub minimum_fee: u64,
2135}
2136
2137#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2139pub enum BuyBitcoinProvider {
2140 #[strum(serialize = "moonpay")]
2141 Moonpay,
2142}
2143
2144#[derive(Debug, Serialize)]
2146pub struct PrepareBuyBitcoinRequest {
2147 pub provider: BuyBitcoinProvider,
2148 pub amount_sat: u64,
2149}
2150
2151#[derive(Clone, Debug, Serialize)]
2153pub struct PrepareBuyBitcoinResponse {
2154 pub provider: BuyBitcoinProvider,
2155 pub amount_sat: u64,
2156 pub fees_sat: u64,
2157}
2158
2159#[derive(Clone, Debug, Serialize)]
2161pub struct BuyBitcoinRequest {
2162 pub prepare_response: PrepareBuyBitcoinResponse,
2163
2164 pub redirect_url: Option<String>,
2168}
2169
2170#[derive(Clone, Debug)]
2172pub struct LogEntry {
2173 pub line: String,
2174 pub level: String,
2175}
2176
2177#[derive(Clone, Debug, Serialize, Deserialize)]
2178struct InternalLeaf {
2179 pub output: String,
2180 pub version: u8,
2181}
2182impl From<InternalLeaf> for Leaf {
2183 fn from(value: InternalLeaf) -> Self {
2184 Leaf {
2185 output: value.output,
2186 version: value.version,
2187 }
2188 }
2189}
2190impl From<Leaf> for InternalLeaf {
2191 fn from(value: Leaf) -> Self {
2192 InternalLeaf {
2193 output: value.output,
2194 version: value.version,
2195 }
2196 }
2197}
2198
2199#[derive(Clone, Debug, Serialize, Deserialize)]
2200pub(super) struct InternalSwapTree {
2201 claim_leaf: InternalLeaf,
2202 refund_leaf: InternalLeaf,
2203}
2204impl From<InternalSwapTree> for SwapTree {
2205 fn from(value: InternalSwapTree) -> Self {
2206 SwapTree {
2207 claim_leaf: value.claim_leaf.into(),
2208 refund_leaf: value.refund_leaf.into(),
2209 }
2210 }
2211}
2212impl From<SwapTree> for InternalSwapTree {
2213 fn from(value: SwapTree) -> Self {
2214 InternalSwapTree {
2215 claim_leaf: value.claim_leaf.into(),
2216 refund_leaf: value.refund_leaf.into(),
2217 }
2218 }
2219}
2220
2221#[derive(Debug, Serialize)]
2223pub struct PrepareLnUrlPayRequest {
2224 pub data: LnUrlPayRequestData,
2226 pub amount: PayAmount,
2228 pub bip353_address: Option<String>,
2231 pub comment: Option<String>,
2233 pub validate_success_action_url: Option<bool>,
2236}
2237
2238#[derive(Debug, Serialize)]
2240pub struct PrepareLnUrlPayResponse {
2241 pub destination: SendDestination,
2243 pub fees_sat: u64,
2245 pub data: LnUrlPayRequestData,
2247 pub comment: Option<String>,
2249 pub success_action: Option<SuccessAction>,
2252}
2253
2254#[derive(Debug, Serialize)]
2256pub struct LnUrlPayRequest {
2257 pub prepare_response: PrepareLnUrlPayResponse,
2259}
2260
2261#[derive(Serialize)]
2273#[allow(clippy::large_enum_variant)]
2274pub enum LnUrlPayResult {
2275 EndpointSuccess { data: LnUrlPaySuccessData },
2276 EndpointError { data: LnUrlErrorData },
2277 PayError { data: LnUrlPayErrorData },
2278}
2279
2280#[derive(Serialize)]
2281pub struct LnUrlPaySuccessData {
2282 pub payment: Payment,
2283 pub success_action: Option<SuccessActionProcessed>,
2284}
2285
2286#[derive(Debug, Clone)]
2287pub enum Transaction {
2288 Liquid(boltz_client::elements::Transaction),
2289 Bitcoin(boltz_client::bitcoin::Transaction),
2290}
2291
2292impl Transaction {
2293 pub(crate) fn txid(&self) -> String {
2294 match self {
2295 Transaction::Liquid(tx) => tx.txid().to_hex(),
2296 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2297 }
2298 }
2299}
2300
2301#[derive(Debug, Clone)]
2302pub enum Utxo {
2303 Liquid(
2304 Box<(
2305 boltz_client::elements::OutPoint,
2306 boltz_client::elements::TxOut,
2307 )>,
2308 ),
2309 Bitcoin(
2310 (
2311 boltz_client::bitcoin::OutPoint,
2312 boltz_client::bitcoin::TxOut,
2313 ),
2314 ),
2315}
2316
2317impl Utxo {
2318 pub(crate) fn as_bitcoin(
2319 &self,
2320 ) -> Option<&(
2321 boltz_client::bitcoin::OutPoint,
2322 boltz_client::bitcoin::TxOut,
2323 )> {
2324 match self {
2325 Utxo::Liquid(_) => None,
2326 Utxo::Bitcoin(utxo) => Some(utxo),
2327 }
2328 }
2329
2330 pub(crate) fn as_liquid(
2331 &self,
2332 ) -> Option<
2333 Box<(
2334 boltz_client::elements::OutPoint,
2335 boltz_client::elements::TxOut,
2336 )>,
2337 > {
2338 match self {
2339 Utxo::Bitcoin(_) => None,
2340 Utxo::Liquid(utxo) => Some(utxo.clone()),
2341 }
2342 }
2343}
2344
2345#[derive(Debug, Clone)]
2347pub struct FetchPaymentProposedFeesRequest {
2348 pub swap_id: String,
2349}
2350
2351#[derive(Debug, Clone, Serialize)]
2353pub struct FetchPaymentProposedFeesResponse {
2354 pub swap_id: String,
2355 pub fees_sat: u64,
2356 pub payer_amount_sat: u64,
2358 pub receiver_amount_sat: u64,
2360}
2361
2362#[derive(Debug, Clone)]
2364pub struct AcceptPaymentProposedFeesRequest {
2365 pub response: FetchPaymentProposedFeesResponse,
2366}
2367
2368#[derive(Clone, Debug)]
2369pub struct History<T> {
2370 pub txid: T,
2371 pub height: i32,
2376}
2377pub(crate) type LBtcHistory = History<elements::Txid>;
2378pub(crate) type BtcHistory = History<bitcoin::Txid>;
2379
2380impl<T> History<T> {
2381 pub(crate) fn confirmed(&self) -> bool {
2382 self.height > 0
2383 }
2384}
2385#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2386impl From<electrum_client::GetHistoryRes> for BtcHistory {
2387 fn from(value: electrum_client::GetHistoryRes) -> Self {
2388 Self {
2389 txid: value.tx_hash,
2390 height: value.height,
2391 }
2392 }
2393}
2394impl From<lwk_wollet::History> for LBtcHistory {
2395 fn from(value: lwk_wollet::History) -> Self {
2396 Self::from(&value)
2397 }
2398}
2399impl From<&lwk_wollet::History> for LBtcHistory {
2400 fn from(value: &lwk_wollet::History) -> Self {
2401 Self {
2402 txid: value.txid,
2403 height: value.height,
2404 }
2405 }
2406}
2407pub(crate) type BtcScript = bitcoin::ScriptBuf;
2408pub(crate) type LBtcScript = elements::Script;
2409
2410#[derive(Clone, Debug)]
2411pub struct BtcScriptBalance {
2412 pub confirmed: u64,
2414 pub unconfirmed: i64,
2418}
2419#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2420impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2421 fn from(val: electrum_client::GetBalanceRes) -> Self {
2422 Self {
2423 confirmed: val.confirmed,
2424 unconfirmed: val.unconfirmed,
2425 }
2426 }
2427}
2428
2429#[macro_export]
2430macro_rules! get_invoice_amount {
2431 ($invoice:expr) => {
2432 $invoice
2433 .parse::<Bolt11Invoice>()
2434 .expect("Expecting valid invoice")
2435 .amount_milli_satoshis()
2436 .expect("Expecting valid amount")
2437 / 1000
2438 };
2439}
2440
2441#[macro_export]
2442macro_rules! get_invoice_description {
2443 ($invoice:expr) => {
2444 match $invoice
2445 .trim()
2446 .parse::<Bolt11Invoice>()
2447 .expect("Expecting valid invoice")
2448 .description()
2449 {
2450 Bolt11InvoiceDescription::Direct(msg) => Some(msg.to_string()),
2451 Bolt11InvoiceDescription::Hash(_) => None,
2452 }
2453 };
2454}
2455
2456#[macro_export]
2457macro_rules! get_updated_fields {
2458 ($($var:ident),* $(,)?) => {{
2459 let mut options = Vec::new();
2460 $(
2461 if $var.is_some() {
2462 options.push(stringify!($var).to_string());
2463 }
2464 )*
2465 match options.len() > 0 {
2466 true => Some(options),
2467 false => None,
2468 }
2469 }};
2470}