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 maybe_sync::{MaybeSend, MaybeSync};
15use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
16use rusqlite::ToSql;
17use sdk_common::prelude::*;
18use sdk_common::utils::Arc;
19use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
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";
44pub const BREEZ_SWAP_PROXY_URL: &str = "https://swap.breez.technology/v2";
45
46const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
47
48#[derive(Clone, Debug, Serialize)]
49pub enum BlockchainExplorer {
50 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
51 Electrum { url: String },
52 Esplora {
53 url: String,
54 use_waterfalls: bool,
56 },
57}
58
59#[derive(Clone, Debug, Serialize)]
61pub struct Config {
62 pub liquid_explorer: BlockchainExplorer,
63 pub bitcoin_explorer: BlockchainExplorer,
64 pub working_dir: String,
68 pub network: LiquidNetwork,
69 pub payment_timeout_sec: u64,
71 pub sync_service_url: Option<String>,
74 pub zero_conf_max_amount_sat: Option<u64>,
77 pub breez_api_key: Option<String>,
79 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
83 pub use_default_external_input_parsers: bool,
87 pub onchain_fee_rate_leeway_sat_per_vbyte: Option<u32>,
94 pub asset_metadata: Option<Vec<AssetMetadata>>,
99 pub sideswap_api_key: Option<String>,
101}
102
103impl Config {
104 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
105 pub fn mainnet(breez_api_key: Option<String>) -> Self {
106 Config {
107 liquid_explorer: BlockchainExplorer::Electrum {
108 url: "elements-mainnet.breez.technology:50002".to_string(),
109 },
110 bitcoin_explorer: BlockchainExplorer::Electrum {
111 url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
112 },
113 working_dir: ".".to_string(),
114 network: LiquidNetwork::Mainnet,
115 payment_timeout_sec: 15,
116 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
117 zero_conf_max_amount_sat: None,
118 breez_api_key,
119 external_input_parsers: None,
120 use_default_external_input_parsers: true,
121 onchain_fee_rate_leeway_sat_per_vbyte: None,
122 asset_metadata: None,
123 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
124 }
125 }
126
127 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
128 Config {
129 liquid_explorer: BlockchainExplorer::Esplora {
130 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
131 use_waterfalls: true,
132 },
133 bitcoin_explorer: BlockchainExplorer::Esplora {
134 url: "https://blockstream.info/api/".to_string(),
135 use_waterfalls: false,
136 },
137 working_dir: ".".to_string(),
138 network: LiquidNetwork::Mainnet,
139 payment_timeout_sec: 15,
140 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
141 zero_conf_max_amount_sat: None,
142 breez_api_key,
143 external_input_parsers: None,
144 use_default_external_input_parsers: true,
145 onchain_fee_rate_leeway_sat_per_vbyte: None,
146 asset_metadata: None,
147 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
148 }
149 }
150
151 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
152 pub fn testnet(breez_api_key: Option<String>) -> Self {
153 Config {
154 liquid_explorer: BlockchainExplorer::Electrum {
155 url: "elements-testnet.blockstream.info:50002".to_string(),
156 },
157 bitcoin_explorer: BlockchainExplorer::Electrum {
158 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
159 },
160 working_dir: ".".to_string(),
161 network: LiquidNetwork::Testnet,
162 payment_timeout_sec: 15,
163 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
164 zero_conf_max_amount_sat: None,
165 breez_api_key,
166 external_input_parsers: None,
167 use_default_external_input_parsers: true,
168 onchain_fee_rate_leeway_sat_per_vbyte: None,
169 asset_metadata: None,
170 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
171 }
172 }
173
174 pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
175 Config {
176 liquid_explorer: BlockchainExplorer::Esplora {
177 url: "https://blockstream.info/liquidtestnet/api".to_string(),
178 use_waterfalls: false,
179 },
180 bitcoin_explorer: BlockchainExplorer::Esplora {
181 url: "https://blockstream.info/testnet/api/".to_string(),
182 use_waterfalls: false,
183 },
184 working_dir: ".".to_string(),
185 network: LiquidNetwork::Testnet,
186 payment_timeout_sec: 15,
187 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
188 zero_conf_max_amount_sat: None,
189 breez_api_key,
190 external_input_parsers: None,
191 use_default_external_input_parsers: true,
192 onchain_fee_rate_leeway_sat_per_vbyte: None,
193 asset_metadata: None,
194 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
195 }
196 }
197
198 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
199 pub fn regtest() -> Self {
200 Config {
201 liquid_explorer: BlockchainExplorer::Electrum {
202 url: "localhost:19002".to_string(),
203 },
204 bitcoin_explorer: BlockchainExplorer::Electrum {
205 url: "localhost:19001".to_string(),
206 },
207 working_dir: ".".to_string(),
208 network: LiquidNetwork::Regtest,
209 payment_timeout_sec: 15,
210 sync_service_url: Some("http://localhost:8088".to_string()),
211 zero_conf_max_amount_sat: None,
212 breez_api_key: None,
213 external_input_parsers: None,
214 use_default_external_input_parsers: true,
215 onchain_fee_rate_leeway_sat_per_vbyte: None,
216 asset_metadata: None,
217 sideswap_api_key: None,
218 }
219 }
220
221 pub fn regtest_esplora() -> Self {
222 Config {
223 liquid_explorer: BlockchainExplorer::Esplora {
224 url: "http://localhost:3120/api".to_string(),
225 use_waterfalls: true,
226 },
227 bitcoin_explorer: BlockchainExplorer::Esplora {
228 url: "http://localhost:4002/api".to_string(),
229 use_waterfalls: false,
230 },
231 working_dir: ".".to_string(),
232 network: LiquidNetwork::Regtest,
233 payment_timeout_sec: 15,
234 sync_service_url: Some("http://localhost:8089".to_string()),
235 zero_conf_max_amount_sat: None,
236 breez_api_key: None,
237 external_input_parsers: None,
238 use_default_external_input_parsers: true,
239 onchain_fee_rate_leeway_sat_per_vbyte: None,
240 asset_metadata: None,
241 sideswap_api_key: None,
242 }
243 }
244
245 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
246 Ok(PathBuf::from(base_dir)
247 .join(match self.network {
248 LiquidNetwork::Mainnet => "mainnet",
249 LiquidNetwork::Testnet => "testnet",
250 LiquidNetwork::Regtest => "regtest",
251 })
252 .join(fingerprint_hex)
253 .to_str()
254 .ok_or(anyhow::anyhow!(
255 "Could not get retrieve current wallet directory"
256 ))?
257 .to_string())
258 }
259
260 pub fn zero_conf_max_amount_sat(&self) -> u64 {
261 self.zero_conf_max_amount_sat
262 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
263 }
264
265 pub(crate) fn lbtc_asset_id(&self) -> String {
266 utils::lbtc_asset_id(self.network).to_string()
267 }
268
269 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
270 let mut external_input_parsers = Vec::new();
271 if self.use_default_external_input_parsers {
272 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
273 .iter()
274 .map(|(id, regex, url)| ExternalInputParser {
275 provider_id: id.to_string(),
276 input_regex: regex.to_string(),
277 parser_url: url.to_string(),
278 })
279 .collect::<Vec<_>>();
280 external_input_parsers.extend(default_parsers);
281 }
282 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
283
284 external_input_parsers
285 }
286
287 pub(crate) fn default_boltz_url(&self) -> &str {
288 match self.network {
289 LiquidNetwork::Mainnet => {
290 if self.breez_api_key.is_some() {
291 BREEZ_SWAP_PROXY_URL
292 } else {
293 BOLTZ_MAINNET_URL_V2
294 }
295 }
296 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
297 LiquidNetwork::Regtest => "http://localhost:8387/v2",
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 an 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, Serialize)]
577pub enum PaymentMethod {
578 #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
579 Lightning,
580 Bolt11Invoice,
581 Bolt12Offer,
582 BitcoinAddress,
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 pub amount: Option<ReceiveAmount>,
604}
605
606#[derive(Debug, Serialize, Clone)]
608pub struct PrepareReceiveResponse {
609 pub payment_method: PaymentMethod,
610 pub fees_sat: u64,
619 pub amount: Option<ReceiveAmount>,
621 pub min_payer_amount_sat: Option<u64>,
625 pub max_payer_amount_sat: Option<u64>,
629 pub swapper_feerate: Option<f64>,
633}
634
635#[derive(Debug, Serialize)]
637pub struct ReceivePaymentRequest {
638 pub prepare_response: PrepareReceiveResponse,
639 pub description: Option<String>,
641 pub use_description_hash: Option<bool>,
643}
644
645#[derive(Debug, Serialize)]
647pub struct ReceivePaymentResponse {
648 pub destination: String,
651}
652
653#[derive(Debug, Serialize)]
655pub struct CreateBolt12InvoiceRequest {
656 pub offer: String,
658 pub invoice_request: String,
660}
661
662#[derive(Debug, Serialize, Clone)]
664pub struct CreateBolt12InvoiceResponse {
665 pub invoice: String,
667}
668
669#[derive(Debug, Serialize)]
671pub struct Limits {
672 pub min_sat: u64,
673 pub max_sat: u64,
674 pub max_zero_conf_sat: u64,
675}
676
677#[derive(Debug, Serialize)]
679pub struct LightningPaymentLimitsResponse {
680 pub send: Limits,
682 pub receive: Limits,
684}
685
686#[derive(Debug, Serialize)]
688pub struct OnchainPaymentLimitsResponse {
689 pub send: Limits,
691 pub receive: Limits,
693}
694
695#[derive(Debug, Serialize, Clone)]
697pub struct PrepareSendRequest {
698 pub destination: String,
701 pub amount: Option<PayAmount>,
704 pub comment: Option<String>,
706}
707
708#[derive(Clone, Debug, Serialize)]
710pub enum SendDestination {
711 LiquidAddress {
712 address_data: liquid::LiquidAddressData,
713 bip353_address: Option<String>,
715 },
716 Bolt11 {
717 invoice: LNInvoice,
718 bip353_address: Option<String>,
720 },
721 Bolt12 {
722 offer: LNOffer,
723 receiver_amount_sat: u64,
724 bip353_address: Option<String>,
726 payer_note: Option<String>,
728 },
729}
730
731#[derive(Debug, Serialize, Clone)]
733pub struct PrepareSendResponse {
734 pub destination: SendDestination,
735 pub amount: Option<PayAmount>,
737 pub fees_sat: Option<u64>,
740 pub estimated_asset_fees: Option<f64>,
744}
745
746#[derive(Debug, Serialize)]
748pub struct SendPaymentRequest {
749 pub prepare_response: PrepareSendResponse,
750 pub use_asset_fees: Option<bool>,
751}
752
753#[derive(Debug, Serialize)]
755pub struct SendPaymentResponse {
756 pub payment: Payment,
757}
758
759#[derive(Debug, Serialize, Clone)]
761pub enum PayAmount {
762 Bitcoin { receiver_amount_sat: u64 },
764
765 Asset {
767 asset_id: String,
768 receiver_amount: f64,
769 estimate_asset_fees: Option<bool>,
770 },
771
772 Drain,
774}
775
776#[derive(Debug, Serialize, Clone)]
778pub struct PreparePayOnchainRequest {
779 pub amount: PayAmount,
781 pub fee_rate_sat_per_vbyte: Option<u32>,
783}
784
785#[derive(Debug, Serialize, Clone)]
787pub struct PreparePayOnchainResponse {
788 pub receiver_amount_sat: u64,
789 pub claim_fees_sat: u64,
790 pub total_fees_sat: u64,
791}
792
793#[derive(Debug, Serialize)]
795pub struct PayOnchainRequest {
796 pub address: String,
797 pub prepare_response: PreparePayOnchainResponse,
798}
799
800#[derive(Debug, Serialize)]
802pub struct PrepareRefundRequest {
803 pub swap_address: String,
805 pub refund_address: String,
807 pub fee_rate_sat_per_vbyte: u32,
809}
810
811#[derive(Debug, Serialize)]
813pub struct PrepareRefundResponse {
814 pub tx_vsize: u32,
815 pub tx_fee_sat: u64,
816 pub last_refund_tx_id: Option<String>,
818}
819
820#[derive(Debug, Serialize)]
822pub struct RefundRequest {
823 pub swap_address: String,
825 pub refund_address: String,
827 pub fee_rate_sat_per_vbyte: u32,
829}
830
831#[derive(Debug, Serialize)]
833pub struct RefundResponse {
834 pub refund_tx_id: String,
835}
836
837#[derive(Clone, Debug, Default, Serialize, Deserialize)]
839pub struct AssetBalance {
840 pub asset_id: String,
841 pub balance_sat: u64,
842 pub name: Option<String>,
843 pub ticker: Option<String>,
844 pub balance: Option<f64>,
845}
846
847#[derive(Debug, Serialize, Deserialize, Default)]
848pub struct BlockchainInfo {
849 pub liquid_tip: u32,
850 pub bitcoin_tip: u32,
851}
852
853#[derive(Copy, Clone)]
854pub(crate) struct ChainTips {
855 pub liquid_tip: u32,
856 pub bitcoin_tip: u32,
857}
858
859#[derive(Debug, Serialize, Deserialize)]
860pub struct WalletInfo {
861 pub balance_sat: u64,
863 pub pending_send_sat: u64,
865 pub pending_receive_sat: u64,
867 pub fingerprint: String,
869 pub pubkey: String,
871 #[serde(default)]
873 pub asset_balances: Vec<AssetBalance>,
874}
875
876impl WalletInfo {
877 pub(crate) fn validate_sufficient_funds(
878 &self,
879 network: LiquidNetwork,
880 amount_sat: u64,
881 fees_sat: Option<u64>,
882 asset_id: &str,
883 ) -> Result<(), PaymentError> {
884 let fees_sat = fees_sat.unwrap_or(0);
885 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
886 ensure_sdk!(
887 amount_sat + fees_sat <= self.balance_sat,
888 PaymentError::InsufficientFunds
889 );
890 } else {
891 match self
892 .asset_balances
893 .iter()
894 .find(|ab| ab.asset_id.eq(asset_id))
895 {
896 Some(asset_balance) => ensure_sdk!(
897 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
898 PaymentError::InsufficientFunds
899 ),
900 None => return Err(PaymentError::InsufficientFunds),
901 }
902 }
903 Ok(())
904 }
905}
906
907#[derive(Debug, Serialize, Deserialize)]
909pub struct GetInfoResponse {
910 pub wallet_info: WalletInfo,
912 #[serde(default)]
914 pub blockchain_info: BlockchainInfo,
915}
916
917#[derive(Clone, Debug, PartialEq)]
919pub struct SignMessageRequest {
920 pub message: String,
921}
922
923#[derive(Clone, Debug, PartialEq)]
925pub struct SignMessageResponse {
926 pub signature: String,
927}
928
929#[derive(Clone, Debug, PartialEq)]
931pub struct CheckMessageRequest {
932 pub message: String,
934 pub pubkey: String,
936 pub signature: String,
938}
939
940#[derive(Clone, Debug, PartialEq)]
942pub struct CheckMessageResponse {
943 pub is_valid: bool,
946}
947
948#[derive(Debug, Serialize)]
950pub struct BackupRequest {
951 pub backup_path: Option<String>,
958}
959
960#[derive(Debug, Serialize)]
962pub struct RestoreRequest {
963 pub backup_path: Option<String>,
964}
965
966#[derive(Default)]
968pub struct ListPaymentsRequest {
969 pub filters: Option<Vec<PaymentType>>,
970 pub states: Option<Vec<PaymentState>>,
971 pub from_timestamp: Option<i64>,
973 pub to_timestamp: Option<i64>,
975 pub offset: Option<u32>,
976 pub limit: Option<u32>,
977 pub details: Option<ListPaymentDetails>,
978 pub sort_ascending: Option<bool>,
979}
980
981#[derive(Debug, Serialize)]
983pub enum ListPaymentDetails {
984 Liquid {
986 asset_id: Option<String>,
988 destination: Option<String>,
990 },
991
992 Bitcoin {
994 address: Option<String>,
996 },
997}
998
999#[derive(Debug, Serialize)]
1001pub enum GetPaymentRequest {
1002 PaymentHash { payment_hash: String },
1004 SwapId { swap_id: String },
1006}
1007
1008#[sdk_macros::async_trait]
1010pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1011 async fn on_bitcoin_block(&self, height: u32);
1012 async fn on_liquid_block(&self, height: u32);
1013}
1014
1015#[derive(Clone, Debug)]
1017pub enum Swap {
1018 Chain(ChainSwap),
1019 Send(SendSwap),
1020 Receive(ReceiveSwap),
1021}
1022impl Swap {
1023 pub(crate) fn id(&self) -> String {
1024 match &self {
1025 Swap::Chain(ChainSwap { id, .. })
1026 | Swap::Send(SendSwap { id, .. })
1027 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1028 }
1029 }
1030
1031 pub(crate) fn version(&self) -> u64 {
1032 match self {
1033 Swap::Chain(ChainSwap { metadata, .. })
1034 | Swap::Send(SendSwap { metadata, .. })
1035 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1036 }
1037 }
1038
1039 pub(crate) fn set_version(&mut self, version: u64) {
1040 match self {
1041 Swap::Chain(chain_swap) => {
1042 chain_swap.metadata.version = version;
1043 }
1044 Swap::Send(send_swap) => {
1045 send_swap.metadata.version = version;
1046 }
1047 Swap::Receive(receive_swap) => {
1048 receive_swap.metadata.version = version;
1049 }
1050 }
1051 }
1052
1053 pub(crate) fn last_updated_at(&self) -> u32 {
1054 match self {
1055 Swap::Chain(ChainSwap { metadata, .. })
1056 | Swap::Send(SendSwap { metadata, .. })
1057 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1058 }
1059 }
1060}
1061impl From<ChainSwap> for Swap {
1062 fn from(swap: ChainSwap) -> Self {
1063 Self::Chain(swap)
1064 }
1065}
1066impl From<SendSwap> for Swap {
1067 fn from(swap: SendSwap) -> Self {
1068 Self::Send(swap)
1069 }
1070}
1071impl From<ReceiveSwap> for Swap {
1072 fn from(swap: ReceiveSwap) -> Self {
1073 Self::Receive(swap)
1074 }
1075}
1076
1077#[derive(Clone, Debug)]
1078pub(crate) enum SwapScriptV2 {
1079 Bitcoin(BtcSwapScript),
1080 Liquid(LBtcSwapScript),
1081}
1082impl SwapScriptV2 {
1083 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1084 match self {
1085 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1086 _ => Err(anyhow!("Invalid chain")),
1087 }
1088 }
1089
1090 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1091 match self {
1092 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1093 _ => Err(anyhow!("Invalid chain")),
1094 }
1095 }
1096}
1097
1098#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1099pub enum Direction {
1100 Incoming = 0,
1101 Outgoing = 1,
1102}
1103impl ToSql for Direction {
1104 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1105 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1106 }
1107}
1108impl FromSql for Direction {
1109 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1110 match value {
1111 ValueRef::Integer(i) => match i as u8 {
1112 0 => Ok(Direction::Incoming),
1113 1 => Ok(Direction::Outgoing),
1114 _ => Err(FromSqlError::OutOfRange(i)),
1115 },
1116 _ => Err(FromSqlError::InvalidType),
1117 }
1118 }
1119}
1120
1121#[derive(Clone, Debug, Default)]
1122pub(crate) struct SwapMetadata {
1123 pub(crate) version: u64,
1125 pub(crate) last_updated_at: u32,
1126 pub(crate) is_local: bool,
1127}
1128
1129#[derive(Clone, Debug, Derivative)]
1133#[derivative(PartialEq)]
1134pub struct ChainSwap {
1135 pub(crate) id: String,
1136 pub(crate) direction: Direction,
1137 pub(crate) claim_address: Option<String>,
1140 pub(crate) lockup_address: String,
1141 pub(crate) refund_address: Option<String>,
1143 pub(crate) timeout_block_height: u32,
1144 pub(crate) preimage: String,
1145 pub(crate) description: Option<String>,
1146 pub(crate) payer_amount_sat: u64,
1148 pub(crate) actual_payer_amount_sat: Option<u64>,
1151 pub(crate) receiver_amount_sat: u64,
1153 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1155 pub(crate) claim_fees_sat: u64,
1156 pub(crate) pair_fees_json: String,
1158 pub(crate) accept_zero_conf: bool,
1159 pub(crate) create_response_json: String,
1161 pub(crate) server_lockup_tx_id: Option<String>,
1163 pub(crate) user_lockup_tx_id: Option<String>,
1165 pub(crate) claim_tx_id: Option<String>,
1167 pub(crate) refund_tx_id: Option<String>,
1169 pub(crate) created_at: u32,
1170 pub(crate) state: PaymentState,
1171 pub(crate) claim_private_key: String,
1172 pub(crate) refund_private_key: String,
1173 pub(crate) auto_accepted_fees: bool,
1174 #[derivative(PartialEq = "ignore")]
1176 pub(crate) metadata: SwapMetadata,
1177}
1178impl ChainSwap {
1179 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1180 utils::decode_keypair(&self.claim_private_key)
1181 }
1182
1183 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1184 utils::decode_keypair(&self.refund_private_key)
1185 }
1186
1187 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1188 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1189 serde_json::from_str(&self.create_response_json).map_err(|e| {
1190 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1191 })?;
1192
1193 Ok(CreateChainResponse {
1194 id: self.id.clone(),
1195 claim_details: internal_create_response.claim_details,
1196 lockup_details: internal_create_response.lockup_details,
1197 })
1198 }
1199
1200 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1201 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1202 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1203
1204 Ok(pair)
1205 }
1206
1207 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1208 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1209 let our_pubkey = self.get_claim_keypair()?.public_key();
1210 let swap_script = match self.direction {
1211 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1212 Side::Claim,
1213 chain_swap_details,
1214 our_pubkey.into(),
1215 )?),
1216 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1217 Side::Claim,
1218 chain_swap_details,
1219 our_pubkey.into(),
1220 )?),
1221 };
1222 Ok(swap_script)
1223 }
1224
1225 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1226 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1227 let our_pubkey = self.get_refund_keypair()?.public_key();
1228 let swap_script = match self.direction {
1229 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1230 Side::Lockup,
1231 chain_swap_details,
1232 our_pubkey.into(),
1233 )?),
1234 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1235 Side::Lockup,
1236 chain_swap_details,
1237 our_pubkey.into(),
1238 )?),
1239 };
1240 Ok(swap_script)
1241 }
1242
1243 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1245 &self,
1246 network: LiquidNetwork,
1247 ) -> SdkResult<ScriptBuf> {
1248 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1249 let script_pubkey = swap_script
1250 .to_address(network.as_bitcoin_chain())
1251 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1252 .script_pubkey();
1253 Ok(script_pubkey)
1254 }
1255
1256 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1257 RefundableSwap {
1258 swap_address: self.lockup_address.clone(),
1259 timestamp: self.created_at,
1260 amount_sat,
1261 last_refund_tx_id: self.refund_tx_id.clone(),
1262 }
1263 }
1264
1265 pub(crate) fn from_boltz_struct_to_json(
1266 create_response: &CreateChainResponse,
1267 expected_swap_id: &str,
1268 ) -> Result<String, PaymentError> {
1269 let internal_create_response =
1270 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1271 create_response,
1272 expected_swap_id,
1273 )?;
1274
1275 let create_response_json =
1276 serde_json::to_string(&internal_create_response).map_err(|e| {
1277 PaymentError::Generic {
1278 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1279 }
1280 })?;
1281
1282 Ok(create_response_json)
1283 }
1284
1285 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1286 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1287 }
1288}
1289
1290#[derive(Clone, Debug, Default)]
1291pub(crate) struct ChainSwapUpdate {
1292 pub(crate) swap_id: String,
1293 pub(crate) to_state: PaymentState,
1294 pub(crate) server_lockup_tx_id: Option<String>,
1295 pub(crate) user_lockup_tx_id: Option<String>,
1296 pub(crate) claim_address: Option<String>,
1297 pub(crate) claim_tx_id: Option<String>,
1298 pub(crate) refund_tx_id: Option<String>,
1299}
1300
1301#[derive(Clone, Debug, Derivative)]
1303#[derivative(PartialEq)]
1304pub struct SendSwap {
1305 pub(crate) id: String,
1306 pub(crate) invoice: String,
1308 pub(crate) bolt12_offer: Option<String>,
1310 pub(crate) payment_hash: Option<String>,
1311 pub(crate) destination_pubkey: Option<String>,
1312 pub(crate) description: Option<String>,
1313 pub(crate) preimage: Option<String>,
1314 pub(crate) payer_amount_sat: u64,
1315 pub(crate) receiver_amount_sat: u64,
1316 pub(crate) pair_fees_json: String,
1318 pub(crate) create_response_json: String,
1320 pub(crate) lockup_tx_id: Option<String>,
1322 pub(crate) refund_address: Option<String>,
1324 pub(crate) refund_tx_id: Option<String>,
1326 pub(crate) created_at: u32,
1327 pub(crate) timeout_block_height: u64,
1328 pub(crate) state: PaymentState,
1329 pub(crate) refund_private_key: String,
1330 #[derivative(PartialEq = "ignore")]
1332 pub(crate) metadata: SwapMetadata,
1333}
1334impl SendSwap {
1335 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1336 utils::decode_keypair(&self.refund_private_key)
1337 }
1338
1339 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1340 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1341 serde_json::from_str(&self.create_response_json).map_err(|e| {
1342 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1343 })?;
1344
1345 let res = CreateSubmarineResponse {
1346 id: self.id.clone(),
1347 accept_zero_conf: internal_create_response.accept_zero_conf,
1348 address: internal_create_response.address.clone(),
1349 bip21: internal_create_response.bip21.clone(),
1350 claim_public_key: crate::utils::json_to_pubkey(
1351 &internal_create_response.claim_public_key,
1352 )?,
1353 expected_amount: internal_create_response.expected_amount,
1354 referral_id: internal_create_response.referral_id,
1355 swap_tree: internal_create_response.swap_tree.clone().into(),
1356 timeout_block_height: internal_create_response.timeout_block_height,
1357 blinding_key: internal_create_response.blinding_key.clone(),
1358 };
1359 Ok(res)
1360 }
1361
1362 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1363 LBtcSwapScript::submarine_from_swap_resp(
1364 &self.get_boltz_create_response()?,
1365 self.get_refund_keypair()?.public_key().into(),
1366 )
1367 .map_err(|e| {
1368 SdkError::generic(format!(
1369 "Failed to create swap script for Send Swap {}: {e:?}",
1370 self.id
1371 ))
1372 })
1373 }
1374
1375 pub(crate) fn from_boltz_struct_to_json(
1376 create_response: &CreateSubmarineResponse,
1377 expected_swap_id: &str,
1378 ) -> Result<String, PaymentError> {
1379 let internal_create_response =
1380 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1381 create_response,
1382 expected_swap_id,
1383 )?;
1384
1385 let create_response_json =
1386 serde_json::to_string(&internal_create_response).map_err(|e| {
1387 PaymentError::Generic {
1388 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1389 }
1390 })?;
1391
1392 Ok(create_response_json)
1393 }
1394}
1395
1396#[derive(Clone, Debug, Derivative)]
1398#[derivative(PartialEq)]
1399pub struct ReceiveSwap {
1400 pub(crate) id: String,
1401 pub(crate) preimage: String,
1402 pub(crate) create_response_json: String,
1404 pub(crate) claim_private_key: String,
1405 pub(crate) invoice: String,
1406 pub(crate) bolt12_offer: Option<String>,
1408 pub(crate) payment_hash: Option<String>,
1409 pub(crate) destination_pubkey: Option<String>,
1410 pub(crate) description: Option<String>,
1411 pub(crate) payer_amount_sat: u64,
1413 pub(crate) receiver_amount_sat: u64,
1414 pub(crate) pair_fees_json: String,
1416 pub(crate) claim_fees_sat: u64,
1417 pub(crate) claim_address: Option<String>,
1419 pub(crate) claim_tx_id: Option<String>,
1421 pub(crate) lockup_tx_id: Option<String>,
1423 pub(crate) mrh_address: String,
1425 pub(crate) mrh_tx_id: Option<String>,
1427 pub(crate) created_at: u32,
1430 pub(crate) timeout_block_height: u32,
1431 pub(crate) state: PaymentState,
1432 #[derivative(PartialEq = "ignore")]
1434 pub(crate) metadata: SwapMetadata,
1435}
1436impl ReceiveSwap {
1437 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1438 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1439 }
1440
1441 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1442 Ok(self
1443 .get_swap_script()?
1444 .funding_addrs
1445 .ok_or(anyhow!("No funding address found"))?
1446 .script_pubkey())
1447 }
1448
1449 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1450 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1451 serde_json::from_str(&self.create_response_json).map_err(|e| {
1452 PaymentError::Generic {
1453 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1454 }
1455 })?;
1456
1457 let res = CreateReverseResponse {
1458 id: self.id.clone(),
1459 invoice: Some(self.invoice.clone()),
1460 swap_tree: internal_create_response.swap_tree.clone().into(),
1461 lockup_address: internal_create_response.lockup_address.clone(),
1462 refund_public_key: crate::utils::json_to_pubkey(
1463 &internal_create_response.refund_public_key,
1464 )?,
1465 timeout_block_height: internal_create_response.timeout_block_height,
1466 onchain_amount: internal_create_response.onchain_amount,
1467 blinding_key: internal_create_response.blinding_key.clone(),
1468 };
1469 Ok(res)
1470 }
1471
1472 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1473 let keypair = self.get_claim_keypair()?;
1474 let create_response =
1475 self.get_boltz_create_response()
1476 .map_err(|e| PaymentError::Generic {
1477 err: format!(
1478 "Failed to create swap script for Receive Swap {}: {e:?}",
1479 self.id
1480 ),
1481 })?;
1482 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1483 .map_err(|e| PaymentError::Generic {
1484 err: format!(
1485 "Failed to create swap script for Receive Swap {}: {e:?}",
1486 self.id
1487 ),
1488 })
1489 }
1490
1491 pub(crate) fn from_boltz_struct_to_json(
1492 create_response: &CreateReverseResponse,
1493 expected_swap_id: &str,
1494 expected_invoice: Option<&str>,
1495 ) -> Result<String, PaymentError> {
1496 let internal_create_response =
1497 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1498 create_response,
1499 expected_swap_id,
1500 expected_invoice,
1501 )?;
1502
1503 let create_response_json =
1504 serde_json::to_string(&internal_create_response).map_err(|e| {
1505 PaymentError::Generic {
1506 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1507 }
1508 })?;
1509
1510 Ok(create_response_json)
1511 }
1512}
1513
1514#[derive(Clone, Debug, PartialEq, Serialize)]
1516pub struct RefundableSwap {
1517 pub swap_address: String,
1518 pub timestamp: u32,
1519 pub amount_sat: u64,
1521 pub last_refund_tx_id: Option<String>,
1523}
1524
1525#[derive(Clone, Debug, Derivative)]
1527#[derivative(PartialEq)]
1528pub(crate) struct Bolt12Offer {
1529 pub(crate) id: String,
1531 pub(crate) description: String,
1533 pub(crate) private_key: String,
1535 pub(crate) webhook_url: Option<String>,
1537 pub(crate) created_at: u32,
1539}
1540impl Bolt12Offer {
1541 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1542 utils::decode_keypair(&self.private_key)
1543 }
1544}
1545impl TryFrom<Bolt12Offer> for Offer {
1546 type Error = SdkError;
1547
1548 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1549 Offer::from_str(&val.id)
1550 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1551 }
1552}
1553
1554#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1556#[strum(serialize_all = "lowercase")]
1557pub enum PaymentState {
1558 #[default]
1559 Created = 0,
1560
1561 Pending = 1,
1581
1582 Complete = 2,
1594
1595 Failed = 3,
1603
1604 TimedOut = 4,
1609
1610 Refundable = 5,
1615
1616 RefundPending = 6,
1622
1623 WaitingFeeAcceptance = 7,
1635}
1636
1637impl ToSql for PaymentState {
1638 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1639 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1640 }
1641}
1642impl FromSql for PaymentState {
1643 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1644 match value {
1645 ValueRef::Integer(i) => match i as u8 {
1646 0 => Ok(PaymentState::Created),
1647 1 => Ok(PaymentState::Pending),
1648 2 => Ok(PaymentState::Complete),
1649 3 => Ok(PaymentState::Failed),
1650 4 => Ok(PaymentState::TimedOut),
1651 5 => Ok(PaymentState::Refundable),
1652 6 => Ok(PaymentState::RefundPending),
1653 7 => Ok(PaymentState::WaitingFeeAcceptance),
1654 _ => Err(FromSqlError::OutOfRange(i)),
1655 },
1656 _ => Err(FromSqlError::InvalidType),
1657 }
1658 }
1659}
1660
1661impl PaymentState {
1662 pub(crate) fn is_refundable(&self) -> bool {
1663 matches!(
1664 self,
1665 PaymentState::Refundable
1666 | PaymentState::RefundPending
1667 | PaymentState::WaitingFeeAcceptance
1668 )
1669 }
1670}
1671
1672#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1673#[strum(serialize_all = "lowercase")]
1674pub enum PaymentType {
1675 Receive = 0,
1676 Send = 1,
1677}
1678impl From<Direction> for PaymentType {
1679 fn from(value: Direction) -> Self {
1680 match value {
1681 Direction::Incoming => Self::Receive,
1682 Direction::Outgoing => Self::Send,
1683 }
1684 }
1685}
1686impl ToSql for PaymentType {
1687 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1688 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1689 }
1690}
1691impl FromSql for PaymentType {
1692 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1693 match value {
1694 ValueRef::Integer(i) => match i as u8 {
1695 0 => Ok(PaymentType::Receive),
1696 1 => Ok(PaymentType::Send),
1697 _ => Err(FromSqlError::OutOfRange(i)),
1698 },
1699 _ => Err(FromSqlError::InvalidType),
1700 }
1701 }
1702}
1703
1704#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1705pub enum PaymentStatus {
1706 Pending = 0,
1707 Complete = 1,
1708}
1709impl ToSql for PaymentStatus {
1710 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1711 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1712 }
1713}
1714impl FromSql for PaymentStatus {
1715 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1716 match value {
1717 ValueRef::Integer(i) => match i as u8 {
1718 0 => Ok(PaymentStatus::Pending),
1719 1 => Ok(PaymentStatus::Complete),
1720 _ => Err(FromSqlError::OutOfRange(i)),
1721 },
1722 _ => Err(FromSqlError::InvalidType),
1723 }
1724 }
1725}
1726
1727#[derive(Debug, Clone, Serialize)]
1728pub struct PaymentTxData {
1729 pub tx_id: String,
1731
1732 pub timestamp: Option<u32>,
1734
1735 pub asset_id: String,
1737
1738 pub amount: u64,
1742
1743 pub fees_sat: u64,
1745
1746 pub payment_type: PaymentType,
1747
1748 pub is_confirmed: bool,
1750
1751 pub unblinding_data: Option<String>,
1754}
1755
1756#[derive(Debug, Clone, Serialize)]
1757pub enum PaymentSwapType {
1758 Receive,
1759 Send,
1760 Chain,
1761}
1762
1763#[derive(Debug, Clone, Serialize)]
1764pub struct PaymentSwapData {
1765 pub swap_id: String,
1766
1767 pub swap_type: PaymentSwapType,
1768
1769 pub created_at: u32,
1771
1772 pub expiration_blockheight: u32,
1774
1775 pub preimage: Option<String>,
1776 pub invoice: Option<String>,
1777 pub bolt12_offer: Option<String>,
1778 pub payment_hash: Option<String>,
1779 pub destination_pubkey: Option<String>,
1780 pub description: String,
1781
1782 pub payer_amount_sat: u64,
1784
1785 pub receiver_amount_sat: u64,
1787
1788 pub swapper_fees_sat: u64,
1790
1791 pub refund_tx_id: Option<String>,
1792 pub refund_tx_amount_sat: Option<u64>,
1793
1794 pub bitcoin_address: Option<String>,
1797
1798 pub status: PaymentState,
1800}
1801
1802#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1804pub struct LnUrlInfo {
1805 pub ln_address: Option<String>,
1806 pub lnurl_pay_comment: Option<String>,
1807 pub lnurl_pay_domain: Option<String>,
1808 pub lnurl_pay_metadata: Option<String>,
1809 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1810 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1811 pub lnurl_withdraw_endpoint: Option<String>,
1812}
1813
1814#[derive(Debug, Clone, Serialize)]
1818pub struct AssetMetadata {
1819 pub asset_id: String,
1821 pub name: String,
1823 pub ticker: String,
1825 pub precision: u8,
1828 pub fiat_id: Option<String>,
1830}
1831
1832impl AssetMetadata {
1833 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1834 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1835 }
1836
1837 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1838 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1839 }
1840}
1841
1842#[derive(Clone, Debug, PartialEq, Serialize)]
1845pub struct AssetInfo {
1846 pub name: String,
1848 pub ticker: String,
1850 pub amount: f64,
1853 pub fees: Option<f64>,
1856}
1857
1858#[derive(Debug, Clone, PartialEq, Serialize)]
1860#[allow(clippy::large_enum_variant)]
1861pub enum PaymentDetails {
1862 Lightning {
1864 swap_id: String,
1865
1866 description: String,
1868
1869 liquid_expiration_blockheight: u32,
1871
1872 preimage: Option<String>,
1874
1875 invoice: Option<String>,
1879
1880 bolt12_offer: Option<String>,
1881
1882 payment_hash: Option<String>,
1884
1885 destination_pubkey: Option<String>,
1887
1888 lnurl_info: Option<LnUrlInfo>,
1890
1891 bip353_address: Option<String>,
1893
1894 claim_tx_id: Option<String>,
1896
1897 refund_tx_id: Option<String>,
1899
1900 refund_tx_amount_sat: Option<u64>,
1902 },
1903 Liquid {
1905 destination: String,
1907
1908 description: String,
1910
1911 asset_id: String,
1913
1914 asset_info: Option<AssetInfo>,
1916
1917 lnurl_info: Option<LnUrlInfo>,
1919
1920 bip353_address: Option<String>,
1922 },
1923 Bitcoin {
1925 swap_id: String,
1926
1927 bitcoin_address: String,
1929
1930 description: String,
1932
1933 auto_accepted_fees: bool,
1937
1938 liquid_expiration_blockheight: Option<u32>,
1941
1942 bitcoin_expiration_blockheight: Option<u32>,
1945
1946 lockup_tx_id: Option<String>,
1948
1949 claim_tx_id: Option<String>,
1951
1952 refund_tx_id: Option<String>,
1954
1955 refund_tx_amount_sat: Option<u64>,
1957 },
1958}
1959
1960impl PaymentDetails {
1961 pub(crate) fn get_swap_id(&self) -> Option<String> {
1962 match self {
1963 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
1964 Some(swap_id.clone())
1965 }
1966 Self::Liquid { .. } => None,
1967 }
1968 }
1969
1970 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
1971 match self {
1972 Self::Lightning {
1973 refund_tx_amount_sat,
1974 ..
1975 }
1976 | Self::Bitcoin {
1977 refund_tx_amount_sat,
1978 ..
1979 } => *refund_tx_amount_sat,
1980 Self::Liquid { .. } => None,
1981 }
1982 }
1983
1984 pub(crate) fn get_description(&self) -> Option<String> {
1985 match self {
1986 Self::Lightning { description, .. }
1987 | Self::Bitcoin { description, .. }
1988 | Self::Liquid { description, .. } => Some(description.clone()),
1989 }
1990 }
1991
1992 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
1993 match self {
1994 Self::Liquid { asset_id, .. } => {
1995 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
1996 }
1997 _ => true,
1998 }
1999 }
2000}
2001
2002#[derive(Debug, Clone, PartialEq, Serialize)]
2006pub struct Payment {
2007 pub destination: Option<String>,
2010
2011 pub tx_id: Option<String>,
2012
2013 pub unblinding_data: Option<String>,
2016
2017 pub timestamp: u32,
2023
2024 pub amount_sat: u64,
2028
2029 pub fees_sat: u64,
2043
2044 pub swapper_fees_sat: Option<u64>,
2047
2048 pub payment_type: PaymentType,
2050
2051 pub status: PaymentState,
2057
2058 pub details: PaymentDetails,
2061}
2062impl Payment {
2063 pub(crate) fn from_pending_swap(
2064 swap: PaymentSwapData,
2065 payment_type: PaymentType,
2066 payment_details: PaymentDetails,
2067 ) -> Payment {
2068 let amount_sat = match payment_type {
2069 PaymentType::Receive => swap.receiver_amount_sat,
2070 PaymentType::Send => swap.payer_amount_sat,
2071 };
2072
2073 Payment {
2074 destination: swap.invoice.clone(),
2075 tx_id: None,
2076 unblinding_data: None,
2077 timestamp: swap.created_at,
2078 amount_sat,
2079 fees_sat: swap
2080 .payer_amount_sat
2081 .saturating_sub(swap.receiver_amount_sat),
2082 swapper_fees_sat: Some(swap.swapper_fees_sat),
2083 payment_type,
2084 status: swap.status,
2085 details: payment_details,
2086 }
2087 }
2088
2089 pub(crate) fn from_tx_data(
2090 tx: PaymentTxData,
2091 swap: Option<PaymentSwapData>,
2092 details: PaymentDetails,
2093 ) -> Payment {
2094 let (amount_sat, fees_sat) = match swap.as_ref() {
2095 Some(s) => match tx.payment_type {
2096 PaymentType::Receive => (tx.amount, s.payer_amount_sat.saturating_sub(tx.amount)),
2100 PaymentType::Send => (
2101 s.receiver_amount_sat,
2102 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2103 ),
2104 },
2105 None => {
2106 let (amount_sat, fees_sat) = match tx.payment_type {
2107 PaymentType::Receive => (tx.amount, 0),
2108 PaymentType::Send => (tx.amount, tx.fees_sat),
2109 };
2110 match details {
2113 PaymentDetails::Liquid {
2114 asset_info: Some(ref asset_info),
2115 ..
2116 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2117 _ => (amount_sat, fees_sat),
2118 }
2119 }
2120 };
2121 Payment {
2122 tx_id: Some(tx.tx_id),
2123 unblinding_data: tx.unblinding_data,
2124 destination: match &swap {
2128 Some(PaymentSwapData {
2129 swap_type: PaymentSwapType::Receive,
2130 invoice,
2131 ..
2132 }) => invoice.clone(),
2133 Some(PaymentSwapData {
2134 swap_type: PaymentSwapType::Send,
2135 invoice,
2136 bolt12_offer,
2137 ..
2138 }) => bolt12_offer.clone().or(invoice.clone()),
2139 Some(PaymentSwapData {
2140 swap_type: PaymentSwapType::Chain,
2141 bitcoin_address,
2142 ..
2143 }) => bitcoin_address.clone(),
2144 _ => match &details {
2145 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2146 _ => None,
2147 },
2148 },
2149 timestamp: tx
2150 .timestamp
2151 .or(swap.as_ref().map(|s| s.created_at))
2152 .unwrap_or(utils::now()),
2153 amount_sat,
2154 fees_sat,
2155 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2156 payment_type: tx.payment_type,
2157 status: match &swap {
2158 Some(swap) => swap.status,
2159 None => match tx.is_confirmed {
2160 true => PaymentState::Complete,
2161 false => PaymentState::Pending,
2162 },
2163 },
2164 details,
2165 }
2166 }
2167
2168 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2169 match self.details.clone() {
2170 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2171 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2172 PaymentDetails::Liquid { .. } => None,
2173 }
2174 .flatten()
2175 }
2176}
2177
2178#[derive(Deserialize, Serialize, Clone, Debug)]
2180#[serde(rename_all = "camelCase")]
2181pub struct RecommendedFees {
2182 pub fastest_fee: u64,
2183 pub half_hour_fee: u64,
2184 pub hour_fee: u64,
2185 pub economy_fee: u64,
2186 pub minimum_fee: u64,
2187}
2188
2189#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2191pub enum BuyBitcoinProvider {
2192 #[strum(serialize = "moonpay")]
2193 Moonpay,
2194}
2195
2196#[derive(Debug, Serialize)]
2198pub struct PrepareBuyBitcoinRequest {
2199 pub provider: BuyBitcoinProvider,
2200 pub amount_sat: u64,
2201}
2202
2203#[derive(Clone, Debug, Serialize)]
2205pub struct PrepareBuyBitcoinResponse {
2206 pub provider: BuyBitcoinProvider,
2207 pub amount_sat: u64,
2208 pub fees_sat: u64,
2209}
2210
2211#[derive(Clone, Debug, Serialize)]
2213pub struct BuyBitcoinRequest {
2214 pub prepare_response: PrepareBuyBitcoinResponse,
2215
2216 pub redirect_url: Option<String>,
2220}
2221
2222#[derive(Clone, Debug)]
2224pub struct LogEntry {
2225 pub line: String,
2226 pub level: String,
2227}
2228
2229#[derive(Clone, Debug, Serialize, Deserialize)]
2230struct InternalLeaf {
2231 pub output: String,
2232 pub version: u8,
2233}
2234impl From<InternalLeaf> for Leaf {
2235 fn from(value: InternalLeaf) -> Self {
2236 Leaf {
2237 output: value.output,
2238 version: value.version,
2239 }
2240 }
2241}
2242impl From<Leaf> for InternalLeaf {
2243 fn from(value: Leaf) -> Self {
2244 InternalLeaf {
2245 output: value.output,
2246 version: value.version,
2247 }
2248 }
2249}
2250
2251#[derive(Clone, Debug, Serialize, Deserialize)]
2252pub(super) struct InternalSwapTree {
2253 claim_leaf: InternalLeaf,
2254 refund_leaf: InternalLeaf,
2255}
2256impl From<InternalSwapTree> for SwapTree {
2257 fn from(value: InternalSwapTree) -> Self {
2258 SwapTree {
2259 claim_leaf: value.claim_leaf.into(),
2260 refund_leaf: value.refund_leaf.into(),
2261 }
2262 }
2263}
2264impl From<SwapTree> for InternalSwapTree {
2265 fn from(value: SwapTree) -> Self {
2266 InternalSwapTree {
2267 claim_leaf: value.claim_leaf.into(),
2268 refund_leaf: value.refund_leaf.into(),
2269 }
2270 }
2271}
2272
2273#[derive(Debug, Serialize)]
2275pub struct PrepareLnUrlPayRequest {
2276 pub data: LnUrlPayRequestData,
2278 pub amount: PayAmount,
2280 pub bip353_address: Option<String>,
2283 pub comment: Option<String>,
2285 pub validate_success_action_url: Option<bool>,
2288}
2289
2290#[derive(Debug, Serialize)]
2292pub struct PrepareLnUrlPayResponse {
2293 pub destination: SendDestination,
2295 pub fees_sat: u64,
2297 pub data: LnUrlPayRequestData,
2299 pub amount: PayAmount,
2301 pub comment: Option<String>,
2303 pub success_action: Option<SuccessAction>,
2306}
2307
2308#[derive(Debug, Serialize)]
2310pub struct LnUrlPayRequest {
2311 pub prepare_response: PrepareLnUrlPayResponse,
2313}
2314
2315#[derive(Serialize)]
2327#[allow(clippy::large_enum_variant)]
2328pub enum LnUrlPayResult {
2329 EndpointSuccess { data: LnUrlPaySuccessData },
2330 EndpointError { data: LnUrlErrorData },
2331 PayError { data: LnUrlPayErrorData },
2332}
2333
2334#[derive(Serialize)]
2335pub struct LnUrlPaySuccessData {
2336 pub payment: Payment,
2337 pub success_action: Option<SuccessActionProcessed>,
2338}
2339
2340#[derive(Debug, Clone)]
2341pub enum Transaction {
2342 Liquid(boltz_client::elements::Transaction),
2343 Bitcoin(boltz_client::bitcoin::Transaction),
2344}
2345
2346impl Transaction {
2347 pub(crate) fn txid(&self) -> String {
2348 match self {
2349 Transaction::Liquid(tx) => tx.txid().to_hex(),
2350 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2351 }
2352 }
2353}
2354
2355#[derive(Debug, Clone)]
2356pub enum Utxo {
2357 Liquid(
2358 Box<(
2359 boltz_client::elements::OutPoint,
2360 boltz_client::elements::TxOut,
2361 )>,
2362 ),
2363 Bitcoin(
2364 (
2365 boltz_client::bitcoin::OutPoint,
2366 boltz_client::bitcoin::TxOut,
2367 ),
2368 ),
2369}
2370
2371impl Utxo {
2372 pub(crate) fn as_bitcoin(
2373 &self,
2374 ) -> Option<&(
2375 boltz_client::bitcoin::OutPoint,
2376 boltz_client::bitcoin::TxOut,
2377 )> {
2378 match self {
2379 Utxo::Liquid(_) => None,
2380 Utxo::Bitcoin(utxo) => Some(utxo),
2381 }
2382 }
2383
2384 pub(crate) fn as_liquid(
2385 &self,
2386 ) -> Option<
2387 Box<(
2388 boltz_client::elements::OutPoint,
2389 boltz_client::elements::TxOut,
2390 )>,
2391 > {
2392 match self {
2393 Utxo::Bitcoin(_) => None,
2394 Utxo::Liquid(utxo) => Some(utxo.clone()),
2395 }
2396 }
2397}
2398
2399#[derive(Debug, Clone)]
2401pub struct FetchPaymentProposedFeesRequest {
2402 pub swap_id: String,
2403}
2404
2405#[derive(Debug, Clone, Serialize)]
2407pub struct FetchPaymentProposedFeesResponse {
2408 pub swap_id: String,
2409 pub fees_sat: u64,
2410 pub payer_amount_sat: u64,
2412 pub receiver_amount_sat: u64,
2414}
2415
2416#[derive(Debug, Clone)]
2418pub struct AcceptPaymentProposedFeesRequest {
2419 pub response: FetchPaymentProposedFeesResponse,
2420}
2421
2422#[derive(Clone, Debug)]
2423pub struct History<T> {
2424 pub txid: T,
2425 pub height: i32,
2430}
2431pub(crate) type LBtcHistory = History<elements::Txid>;
2432pub(crate) type BtcHistory = History<bitcoin::Txid>;
2433
2434impl<T> History<T> {
2435 pub(crate) fn confirmed(&self) -> bool {
2436 self.height > 0
2437 }
2438}
2439#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2440impl From<electrum_client::GetHistoryRes> for BtcHistory {
2441 fn from(value: electrum_client::GetHistoryRes) -> Self {
2442 Self {
2443 txid: value.tx_hash,
2444 height: value.height,
2445 }
2446 }
2447}
2448impl From<lwk_wollet::History> for LBtcHistory {
2449 fn from(value: lwk_wollet::History) -> Self {
2450 Self::from(&value)
2451 }
2452}
2453impl From<&lwk_wollet::History> for LBtcHistory {
2454 fn from(value: &lwk_wollet::History) -> Self {
2455 Self {
2456 txid: value.txid,
2457 height: value.height,
2458 }
2459 }
2460}
2461pub(crate) type BtcScript = bitcoin::ScriptBuf;
2462pub(crate) type LBtcScript = elements::Script;
2463
2464#[derive(Clone, Debug)]
2465pub struct BtcScriptBalance {
2466 pub confirmed: u64,
2468 pub unconfirmed: i64,
2472}
2473#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2474impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2475 fn from(val: electrum_client::GetBalanceRes) -> Self {
2476 Self {
2477 confirmed: val.confirmed,
2478 unconfirmed: val.unconfirmed,
2479 }
2480 }
2481}
2482
2483#[macro_export]
2484macro_rules! get_updated_fields {
2485 ($($var:ident),* $(,)?) => {{
2486 let mut options = Vec::new();
2487 $(
2488 if $var.is_some() {
2489 options.push(stringify!($var).to_string());
2490 }
2491 )*
2492 match options.len() > 0 {
2493 true => Some(options),
2494 false => None,
2495 }
2496 }};
2497}