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::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";
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:3120/api".to_string(),
231 use_waterfalls: true,
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:8089".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, 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
702 pub amount: Option<PayAmount>,
705}
706
707#[derive(Clone, Debug, Serialize)]
709pub enum SendDestination {
710 LiquidAddress {
711 address_data: liquid::LiquidAddressData,
712 bip353_address: Option<String>,
714 },
715 Bolt11 {
716 invoice: LNInvoice,
717 bip353_address: Option<String>,
719 },
720 Bolt12 {
721 offer: LNOffer,
722 receiver_amount_sat: u64,
723 bip353_address: Option<String>,
725 },
726}
727
728#[derive(Debug, Serialize, Clone)]
730pub struct PrepareSendResponse {
731 pub destination: SendDestination,
732 pub fees_sat: Option<u64>,
735 pub estimated_asset_fees: Option<f64>,
739}
740
741#[derive(Debug, Serialize)]
743pub struct SendPaymentRequest {
744 pub prepare_response: PrepareSendResponse,
745 pub use_asset_fees: Option<bool>,
746}
747
748#[derive(Debug, Serialize)]
750pub struct SendPaymentResponse {
751 pub payment: Payment,
752}
753
754#[derive(Debug, Serialize, Clone)]
756pub enum PayAmount {
757 Bitcoin { receiver_amount_sat: u64 },
759
760 Asset {
762 asset_id: String,
763 receiver_amount: f64,
764 estimate_asset_fees: Option<bool>,
765 },
766
767 Drain,
769}
770
771#[derive(Debug, Serialize, Clone)]
773pub struct PreparePayOnchainRequest {
774 pub amount: PayAmount,
776 pub fee_rate_sat_per_vbyte: Option<u32>,
778}
779
780#[derive(Debug, Serialize, Clone)]
782pub struct PreparePayOnchainResponse {
783 pub receiver_amount_sat: u64,
784 pub claim_fees_sat: u64,
785 pub total_fees_sat: u64,
786}
787
788#[derive(Debug, Serialize)]
790pub struct PayOnchainRequest {
791 pub address: String,
792 pub prepare_response: PreparePayOnchainResponse,
793}
794
795#[derive(Debug, Serialize)]
797pub struct PrepareRefundRequest {
798 pub swap_address: String,
800 pub refund_address: String,
802 pub fee_rate_sat_per_vbyte: u32,
804}
805
806#[derive(Debug, Serialize)]
808pub struct PrepareRefundResponse {
809 pub tx_vsize: u32,
810 pub tx_fee_sat: u64,
811 pub last_refund_tx_id: Option<String>,
813}
814
815#[derive(Debug, Serialize)]
817pub struct RefundRequest {
818 pub swap_address: String,
820 pub refund_address: String,
822 pub fee_rate_sat_per_vbyte: u32,
824}
825
826#[derive(Debug, Serialize)]
828pub struct RefundResponse {
829 pub refund_tx_id: String,
830}
831
832#[derive(Clone, Debug, Default, Serialize, Deserialize)]
834pub struct AssetBalance {
835 pub asset_id: String,
836 pub balance_sat: u64,
837 pub name: Option<String>,
838 pub ticker: Option<String>,
839 pub balance: Option<f64>,
840}
841
842#[derive(Debug, Serialize, Deserialize, Default)]
843pub struct BlockchainInfo {
844 pub liquid_tip: u32,
845 pub bitcoin_tip: u32,
846}
847
848#[derive(Copy, Clone)]
849pub(crate) struct ChainTips {
850 pub liquid_tip: u32,
851 pub bitcoin_tip: u32,
852}
853
854#[derive(Debug, Serialize, Deserialize)]
855pub struct WalletInfo {
856 pub balance_sat: u64,
858 pub pending_send_sat: u64,
860 pub pending_receive_sat: u64,
862 pub fingerprint: String,
864 pub pubkey: String,
866 #[serde(default)]
868 pub asset_balances: Vec<AssetBalance>,
869}
870
871impl WalletInfo {
872 pub(crate) fn validate_sufficient_funds(
873 &self,
874 network: LiquidNetwork,
875 amount_sat: u64,
876 fees_sat: Option<u64>,
877 asset_id: &str,
878 ) -> Result<(), PaymentError> {
879 let fees_sat = fees_sat.unwrap_or(0);
880 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
881 ensure_sdk!(
882 amount_sat + fees_sat <= self.balance_sat,
883 PaymentError::InsufficientFunds
884 );
885 } else {
886 match self
887 .asset_balances
888 .iter()
889 .find(|ab| ab.asset_id.eq(asset_id))
890 {
891 Some(asset_balance) => ensure_sdk!(
892 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
893 PaymentError::InsufficientFunds
894 ),
895 None => return Err(PaymentError::InsufficientFunds),
896 }
897 }
898 Ok(())
899 }
900}
901
902#[derive(Debug, Serialize, Deserialize)]
904pub struct GetInfoResponse {
905 pub wallet_info: WalletInfo,
907 #[serde(default)]
909 pub blockchain_info: BlockchainInfo,
910}
911
912#[derive(Clone, Debug, PartialEq)]
914pub struct SignMessageRequest {
915 pub message: String,
916}
917
918#[derive(Clone, Debug, PartialEq)]
920pub struct SignMessageResponse {
921 pub signature: String,
922}
923
924#[derive(Clone, Debug, PartialEq)]
926pub struct CheckMessageRequest {
927 pub message: String,
929 pub pubkey: String,
931 pub signature: String,
933}
934
935#[derive(Clone, Debug, PartialEq)]
937pub struct CheckMessageResponse {
938 pub is_valid: bool,
941}
942
943#[derive(Debug, Serialize)]
945pub struct BackupRequest {
946 pub backup_path: Option<String>,
953}
954
955#[derive(Debug, Serialize)]
957pub struct RestoreRequest {
958 pub backup_path: Option<String>,
959}
960
961#[derive(Default)]
963pub struct ListPaymentsRequest {
964 pub filters: Option<Vec<PaymentType>>,
965 pub states: Option<Vec<PaymentState>>,
966 pub from_timestamp: Option<i64>,
968 pub to_timestamp: Option<i64>,
970 pub offset: Option<u32>,
971 pub limit: Option<u32>,
972 pub details: Option<ListPaymentDetails>,
973 pub sort_ascending: Option<bool>,
974}
975
976#[derive(Debug, Serialize)]
978pub enum ListPaymentDetails {
979 Liquid {
981 asset_id: Option<String>,
983 destination: Option<String>,
985 },
986
987 Bitcoin {
989 address: Option<String>,
991 },
992}
993
994#[derive(Debug, Serialize)]
996pub enum GetPaymentRequest {
997 PaymentHash { payment_hash: String },
999 SwapId { swap_id: String },
1001}
1002
1003#[sdk_macros::async_trait]
1005pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1006 async fn on_bitcoin_block(&self, height: u32);
1007 async fn on_liquid_block(&self, height: u32);
1008}
1009
1010#[derive(Clone, Debug)]
1012pub enum Swap {
1013 Chain(ChainSwap),
1014 Send(SendSwap),
1015 Receive(ReceiveSwap),
1016}
1017impl Swap {
1018 pub(crate) fn id(&self) -> String {
1019 match &self {
1020 Swap::Chain(ChainSwap { id, .. })
1021 | Swap::Send(SendSwap { id, .. })
1022 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1023 }
1024 }
1025
1026 pub(crate) fn version(&self) -> u64 {
1027 match self {
1028 Swap::Chain(ChainSwap { metadata, .. })
1029 | Swap::Send(SendSwap { metadata, .. })
1030 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1031 }
1032 }
1033
1034 pub(crate) fn set_version(&mut self, version: u64) {
1035 match self {
1036 Swap::Chain(chain_swap) => {
1037 chain_swap.metadata.version = version;
1038 }
1039 Swap::Send(send_swap) => {
1040 send_swap.metadata.version = version;
1041 }
1042 Swap::Receive(receive_swap) => {
1043 receive_swap.metadata.version = version;
1044 }
1045 }
1046 }
1047
1048 pub(crate) fn is_local(&self) -> bool {
1049 match self {
1050 Swap::Chain(ChainSwap { metadata, .. })
1051 | Swap::Send(SendSwap { metadata, .. })
1052 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.is_local,
1053 }
1054 }
1055
1056 pub(crate) fn last_updated_at(&self) -> u32 {
1057 match self {
1058 Swap::Chain(ChainSwap { metadata, .. })
1059 | Swap::Send(SendSwap { metadata, .. })
1060 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1061 }
1062 }
1063}
1064impl From<ChainSwap> for Swap {
1065 fn from(swap: ChainSwap) -> Self {
1066 Self::Chain(swap)
1067 }
1068}
1069impl From<SendSwap> for Swap {
1070 fn from(swap: SendSwap) -> Self {
1071 Self::Send(swap)
1072 }
1073}
1074impl From<ReceiveSwap> for Swap {
1075 fn from(swap: ReceiveSwap) -> Self {
1076 Self::Receive(swap)
1077 }
1078}
1079
1080#[derive(Clone, Debug)]
1081pub(crate) enum SwapScriptV2 {
1082 Bitcoin(BtcSwapScript),
1083 Liquid(LBtcSwapScript),
1084}
1085impl SwapScriptV2 {
1086 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1087 match self {
1088 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1089 _ => Err(anyhow!("Invalid chain")),
1090 }
1091 }
1092
1093 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1094 match self {
1095 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1096 _ => Err(anyhow!("Invalid chain")),
1097 }
1098 }
1099}
1100
1101#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1102pub enum Direction {
1103 Incoming = 0,
1104 Outgoing = 1,
1105}
1106impl ToSql for Direction {
1107 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1108 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1109 }
1110}
1111impl FromSql for Direction {
1112 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1113 match value {
1114 ValueRef::Integer(i) => match i as u8 {
1115 0 => Ok(Direction::Incoming),
1116 1 => Ok(Direction::Outgoing),
1117 _ => Err(FromSqlError::OutOfRange(i)),
1118 },
1119 _ => Err(FromSqlError::InvalidType),
1120 }
1121 }
1122}
1123
1124#[derive(Clone, Debug, Default)]
1125pub(crate) struct SwapMetadata {
1126 pub(crate) version: u64,
1128 pub(crate) last_updated_at: u32,
1129 pub(crate) is_local: bool,
1130}
1131
1132#[derive(Clone, Debug, Derivative)]
1136#[derivative(PartialEq)]
1137pub struct ChainSwap {
1138 pub(crate) id: String,
1139 pub(crate) direction: Direction,
1140 pub(crate) claim_address: Option<String>,
1142 pub(crate) lockup_address: String,
1143 pub(crate) refund_address: Option<String>,
1145 pub(crate) timeout_block_height: u32,
1146 pub(crate) preimage: String,
1147 pub(crate) description: Option<String>,
1148 pub(crate) payer_amount_sat: u64,
1150 pub(crate) actual_payer_amount_sat: Option<u64>,
1153 pub(crate) receiver_amount_sat: u64,
1155 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1157 pub(crate) claim_fees_sat: u64,
1158 pub(crate) pair_fees_json: String,
1160 pub(crate) accept_zero_conf: bool,
1161 pub(crate) create_response_json: String,
1163 pub(crate) server_lockup_tx_id: Option<String>,
1165 pub(crate) user_lockup_tx_id: Option<String>,
1167 pub(crate) claim_tx_id: Option<String>,
1169 pub(crate) refund_tx_id: Option<String>,
1171 pub(crate) created_at: u32,
1172 pub(crate) state: PaymentState,
1173 pub(crate) claim_private_key: String,
1174 pub(crate) refund_private_key: String,
1175 pub(crate) auto_accepted_fees: bool,
1176 #[derivative(PartialEq = "ignore")]
1178 pub(crate) metadata: SwapMetadata,
1179}
1180impl ChainSwap {
1181 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1182 utils::decode_keypair(&self.claim_private_key)
1183 }
1184
1185 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1186 utils::decode_keypair(&self.refund_private_key)
1187 }
1188
1189 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1190 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1191 serde_json::from_str(&self.create_response_json).map_err(|e| {
1192 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1193 })?;
1194
1195 Ok(CreateChainResponse {
1196 id: self.id.clone(),
1197 claim_details: internal_create_response.claim_details,
1198 lockup_details: internal_create_response.lockup_details,
1199 })
1200 }
1201
1202 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1203 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1204 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1205
1206 Ok(pair)
1207 }
1208
1209 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1210 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1211 let our_pubkey = self.get_claim_keypair()?.public_key();
1212 let swap_script = match self.direction {
1213 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1214 Side::Claim,
1215 chain_swap_details,
1216 our_pubkey.into(),
1217 )?),
1218 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1219 Side::Claim,
1220 chain_swap_details,
1221 our_pubkey.into(),
1222 )?),
1223 };
1224 Ok(swap_script)
1225 }
1226
1227 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1228 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1229 let our_pubkey = self.get_refund_keypair()?.public_key();
1230 let swap_script = match self.direction {
1231 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1232 Side::Lockup,
1233 chain_swap_details,
1234 our_pubkey.into(),
1235 )?),
1236 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1237 Side::Lockup,
1238 chain_swap_details,
1239 our_pubkey.into(),
1240 )?),
1241 };
1242 Ok(swap_script)
1243 }
1244
1245 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1247 &self,
1248 network: LiquidNetwork,
1249 ) -> SdkResult<ScriptBuf> {
1250 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1251 let script_pubkey = swap_script
1252 .to_address(network.as_bitcoin_chain())
1253 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1254 .script_pubkey();
1255 Ok(script_pubkey)
1256 }
1257
1258 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1259 RefundableSwap {
1260 swap_address: self.lockup_address.clone(),
1261 timestamp: self.created_at,
1262 amount_sat,
1263 last_refund_tx_id: self.refund_tx_id.clone(),
1264 }
1265 }
1266
1267 pub(crate) fn from_boltz_struct_to_json(
1268 create_response: &CreateChainResponse,
1269 expected_swap_id: &str,
1270 ) -> Result<String, PaymentError> {
1271 let internal_create_response =
1272 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1273 create_response,
1274 expected_swap_id,
1275 )?;
1276
1277 let create_response_json =
1278 serde_json::to_string(&internal_create_response).map_err(|e| {
1279 PaymentError::Generic {
1280 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1281 }
1282 })?;
1283
1284 Ok(create_response_json)
1285 }
1286
1287 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1288 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1289 }
1290}
1291
1292#[derive(Clone, Debug, Default)]
1293pub(crate) struct ChainSwapUpdate {
1294 pub(crate) swap_id: String,
1295 pub(crate) to_state: PaymentState,
1296 pub(crate) server_lockup_tx_id: Option<String>,
1297 pub(crate) user_lockup_tx_id: Option<String>,
1298 pub(crate) claim_address: Option<String>,
1299 pub(crate) claim_tx_id: Option<String>,
1300 pub(crate) refund_tx_id: Option<String>,
1301}
1302
1303#[derive(Clone, Debug, Derivative)]
1305#[derivative(PartialEq)]
1306pub struct SendSwap {
1307 pub(crate) id: String,
1308 pub(crate) invoice: String,
1310 pub(crate) bolt12_offer: Option<String>,
1312 pub(crate) payment_hash: Option<String>,
1313 pub(crate) destination_pubkey: Option<String>,
1314 pub(crate) description: Option<String>,
1315 pub(crate) preimage: Option<String>,
1316 pub(crate) payer_amount_sat: u64,
1317 pub(crate) receiver_amount_sat: u64,
1318 pub(crate) pair_fees_json: String,
1320 pub(crate) create_response_json: String,
1322 pub(crate) lockup_tx_id: Option<String>,
1324 pub(crate) refund_address: Option<String>,
1326 pub(crate) refund_tx_id: Option<String>,
1328 pub(crate) created_at: u32,
1329 pub(crate) timeout_block_height: u64,
1330 pub(crate) state: PaymentState,
1331 pub(crate) refund_private_key: String,
1332 #[derivative(PartialEq = "ignore")]
1334 pub(crate) metadata: SwapMetadata,
1335}
1336impl SendSwap {
1337 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1338 utils::decode_keypair(&self.refund_private_key)
1339 }
1340
1341 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1342 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1343 serde_json::from_str(&self.create_response_json).map_err(|e| {
1344 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1345 })?;
1346
1347 let res = CreateSubmarineResponse {
1348 id: self.id.clone(),
1349 accept_zero_conf: internal_create_response.accept_zero_conf,
1350 address: internal_create_response.address.clone(),
1351 bip21: internal_create_response.bip21.clone(),
1352 claim_public_key: crate::utils::json_to_pubkey(
1353 &internal_create_response.claim_public_key,
1354 )?,
1355 expected_amount: internal_create_response.expected_amount,
1356 referral_id: internal_create_response.referral_id,
1357 swap_tree: internal_create_response.swap_tree.clone().into(),
1358 timeout_block_height: internal_create_response.timeout_block_height,
1359 blinding_key: internal_create_response.blinding_key.clone(),
1360 };
1361 Ok(res)
1362 }
1363
1364 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1365 LBtcSwapScript::submarine_from_swap_resp(
1366 &self.get_boltz_create_response()?,
1367 self.get_refund_keypair()?.public_key().into(),
1368 )
1369 .map_err(|e| {
1370 SdkError::generic(format!(
1371 "Failed to create swap script for Send Swap {}: {e:?}",
1372 self.id
1373 ))
1374 })
1375 }
1376
1377 pub(crate) fn from_boltz_struct_to_json(
1378 create_response: &CreateSubmarineResponse,
1379 expected_swap_id: &str,
1380 ) -> Result<String, PaymentError> {
1381 let internal_create_response =
1382 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1383 create_response,
1384 expected_swap_id,
1385 )?;
1386
1387 let create_response_json =
1388 serde_json::to_string(&internal_create_response).map_err(|e| {
1389 PaymentError::Generic {
1390 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1391 }
1392 })?;
1393
1394 Ok(create_response_json)
1395 }
1396}
1397
1398#[derive(Clone, Debug, Derivative)]
1400#[derivative(PartialEq)]
1401pub struct ReceiveSwap {
1402 pub(crate) id: String,
1403 pub(crate) preimage: String,
1404 pub(crate) create_response_json: String,
1406 pub(crate) claim_private_key: String,
1407 pub(crate) invoice: String,
1408 pub(crate) bolt12_offer: Option<String>,
1410 pub(crate) payment_hash: Option<String>,
1411 pub(crate) destination_pubkey: Option<String>,
1412 pub(crate) description: Option<String>,
1413 pub(crate) payer_amount_sat: u64,
1415 pub(crate) receiver_amount_sat: u64,
1416 pub(crate) pair_fees_json: String,
1418 pub(crate) claim_fees_sat: u64,
1419 pub(crate) claim_address: Option<String>,
1421 pub(crate) claim_tx_id: Option<String>,
1423 pub(crate) lockup_tx_id: Option<String>,
1425 pub(crate) mrh_address: String,
1427 pub(crate) mrh_tx_id: Option<String>,
1429 pub(crate) created_at: u32,
1432 pub(crate) timeout_block_height: u32,
1433 pub(crate) state: PaymentState,
1434 #[derivative(PartialEq = "ignore")]
1436 pub(crate) metadata: SwapMetadata,
1437}
1438impl ReceiveSwap {
1439 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1440 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1441 }
1442
1443 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1444 Ok(self
1445 .get_swap_script()?
1446 .funding_addrs
1447 .ok_or(anyhow!("No funding address found"))?
1448 .script_pubkey())
1449 }
1450
1451 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1452 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1453 serde_json::from_str(&self.create_response_json).map_err(|e| {
1454 PaymentError::Generic {
1455 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1456 }
1457 })?;
1458
1459 let res = CreateReverseResponse {
1460 id: self.id.clone(),
1461 invoice: Some(self.invoice.clone()),
1462 swap_tree: internal_create_response.swap_tree.clone().into(),
1463 lockup_address: internal_create_response.lockup_address.clone(),
1464 refund_public_key: crate::utils::json_to_pubkey(
1465 &internal_create_response.refund_public_key,
1466 )?,
1467 timeout_block_height: internal_create_response.timeout_block_height,
1468 onchain_amount: internal_create_response.onchain_amount,
1469 blinding_key: internal_create_response.blinding_key.clone(),
1470 };
1471 Ok(res)
1472 }
1473
1474 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1475 let keypair = self.get_claim_keypair()?;
1476 let create_response =
1477 self.get_boltz_create_response()
1478 .map_err(|e| PaymentError::Generic {
1479 err: format!(
1480 "Failed to create swap script for Receive Swap {}: {e:?}",
1481 self.id
1482 ),
1483 })?;
1484 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1485 .map_err(|e| PaymentError::Generic {
1486 err: format!(
1487 "Failed to create swap script for Receive Swap {}: {e:?}",
1488 self.id
1489 ),
1490 })
1491 }
1492
1493 pub(crate) fn from_boltz_struct_to_json(
1494 create_response: &CreateReverseResponse,
1495 expected_swap_id: &str,
1496 expected_invoice: Option<&str>,
1497 ) -> Result<String, PaymentError> {
1498 let internal_create_response =
1499 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1500 create_response,
1501 expected_swap_id,
1502 expected_invoice,
1503 )?;
1504
1505 let create_response_json =
1506 serde_json::to_string(&internal_create_response).map_err(|e| {
1507 PaymentError::Generic {
1508 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1509 }
1510 })?;
1511
1512 Ok(create_response_json)
1513 }
1514}
1515
1516#[derive(Clone, Debug, PartialEq, Serialize)]
1518pub struct RefundableSwap {
1519 pub swap_address: String,
1520 pub timestamp: u32,
1521 pub amount_sat: u64,
1523 pub last_refund_tx_id: Option<String>,
1525}
1526
1527#[derive(Clone, Debug, Derivative)]
1529#[derivative(PartialEq)]
1530pub(crate) struct Bolt12Offer {
1531 pub(crate) id: String,
1533 pub(crate) description: String,
1535 pub(crate) private_key: String,
1537 pub(crate) webhook_url: Option<String>,
1539 pub(crate) created_at: u32,
1541}
1542impl Bolt12Offer {
1543 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1544 utils::decode_keypair(&self.private_key)
1545 }
1546}
1547impl TryFrom<Bolt12Offer> for Offer {
1548 type Error = SdkError;
1549
1550 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1551 Offer::from_str(&val.id)
1552 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1553 }
1554}
1555
1556#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1558#[strum(serialize_all = "lowercase")]
1559pub enum PaymentState {
1560 #[default]
1561 Created = 0,
1562
1563 Pending = 1,
1583
1584 Complete = 2,
1596
1597 Failed = 3,
1605
1606 TimedOut = 4,
1611
1612 Refundable = 5,
1617
1618 RefundPending = 6,
1624
1625 WaitingFeeAcceptance = 7,
1637}
1638
1639impl ToSql for PaymentState {
1640 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1641 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1642 }
1643}
1644impl FromSql for PaymentState {
1645 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1646 match value {
1647 ValueRef::Integer(i) => match i as u8 {
1648 0 => Ok(PaymentState::Created),
1649 1 => Ok(PaymentState::Pending),
1650 2 => Ok(PaymentState::Complete),
1651 3 => Ok(PaymentState::Failed),
1652 4 => Ok(PaymentState::TimedOut),
1653 5 => Ok(PaymentState::Refundable),
1654 6 => Ok(PaymentState::RefundPending),
1655 7 => Ok(PaymentState::WaitingFeeAcceptance),
1656 _ => Err(FromSqlError::OutOfRange(i)),
1657 },
1658 _ => Err(FromSqlError::InvalidType),
1659 }
1660 }
1661}
1662
1663impl PaymentState {
1664 pub(crate) fn is_refundable(&self) -> bool {
1665 matches!(
1666 self,
1667 PaymentState::Refundable
1668 | PaymentState::RefundPending
1669 | PaymentState::WaitingFeeAcceptance
1670 )
1671 }
1672}
1673
1674#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1675#[strum(serialize_all = "lowercase")]
1676pub enum PaymentType {
1677 Receive = 0,
1678 Send = 1,
1679}
1680impl From<Direction> for PaymentType {
1681 fn from(value: Direction) -> Self {
1682 match value {
1683 Direction::Incoming => Self::Receive,
1684 Direction::Outgoing => Self::Send,
1685 }
1686 }
1687}
1688impl ToSql for PaymentType {
1689 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1690 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1691 }
1692}
1693impl FromSql for PaymentType {
1694 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1695 match value {
1696 ValueRef::Integer(i) => match i as u8 {
1697 0 => Ok(PaymentType::Receive),
1698 1 => Ok(PaymentType::Send),
1699 _ => Err(FromSqlError::OutOfRange(i)),
1700 },
1701 _ => Err(FromSqlError::InvalidType),
1702 }
1703 }
1704}
1705
1706#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1707pub enum PaymentStatus {
1708 Pending = 0,
1709 Complete = 1,
1710}
1711impl ToSql for PaymentStatus {
1712 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1713 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1714 }
1715}
1716impl FromSql for PaymentStatus {
1717 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1718 match value {
1719 ValueRef::Integer(i) => match i as u8 {
1720 0 => Ok(PaymentStatus::Pending),
1721 1 => Ok(PaymentStatus::Complete),
1722 _ => Err(FromSqlError::OutOfRange(i)),
1723 },
1724 _ => Err(FromSqlError::InvalidType),
1725 }
1726 }
1727}
1728
1729#[derive(Debug, Clone, Serialize)]
1730pub struct PaymentTxData {
1731 pub tx_id: String,
1733
1734 pub timestamp: Option<u32>,
1736
1737 pub asset_id: String,
1739
1740 pub amount: u64,
1744
1745 pub fees_sat: u64,
1747
1748 pub payment_type: PaymentType,
1749
1750 pub is_confirmed: bool,
1752
1753 pub unblinding_data: Option<String>,
1756}
1757
1758#[derive(Debug, Clone, Serialize)]
1759pub enum PaymentSwapType {
1760 Receive,
1761 Send,
1762 Chain,
1763}
1764
1765#[derive(Debug, Clone, Serialize)]
1766pub struct PaymentSwapData {
1767 pub swap_id: String,
1768
1769 pub swap_type: PaymentSwapType,
1770
1771 pub created_at: u32,
1773
1774 pub expiration_blockheight: u32,
1776
1777 pub preimage: Option<String>,
1778 pub invoice: Option<String>,
1779 pub bolt12_offer: Option<String>,
1780 pub payment_hash: Option<String>,
1781 pub destination_pubkey: Option<String>,
1782 pub description: String,
1783
1784 pub payer_amount_sat: u64,
1786
1787 pub receiver_amount_sat: u64,
1789
1790 pub swapper_fees_sat: u64,
1792
1793 pub refund_tx_id: Option<String>,
1794 pub refund_tx_amount_sat: Option<u64>,
1795
1796 pub claim_address: Option<String>,
1800
1801 pub status: PaymentState,
1803}
1804
1805#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1807pub struct LnUrlInfo {
1808 pub ln_address: Option<String>,
1809 pub lnurl_pay_comment: Option<String>,
1810 pub lnurl_pay_domain: Option<String>,
1811 pub lnurl_pay_metadata: Option<String>,
1812 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1813 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1814 pub lnurl_withdraw_endpoint: Option<String>,
1815}
1816
1817#[derive(Debug, Clone, Serialize)]
1821pub struct AssetMetadata {
1822 pub asset_id: String,
1824 pub name: String,
1826 pub ticker: String,
1828 pub precision: u8,
1831 pub fiat_id: Option<String>,
1833}
1834
1835impl AssetMetadata {
1836 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1837 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1838 }
1839
1840 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1841 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1842 }
1843}
1844
1845#[derive(Clone, Debug, PartialEq, Serialize)]
1848pub struct AssetInfo {
1849 pub name: String,
1851 pub ticker: String,
1853 pub amount: f64,
1856 pub fees: Option<f64>,
1859}
1860
1861#[derive(Debug, Clone, PartialEq, Serialize)]
1863#[allow(clippy::large_enum_variant)]
1864pub enum PaymentDetails {
1865 Lightning {
1867 swap_id: String,
1868
1869 description: String,
1871
1872 liquid_expiration_blockheight: u32,
1874
1875 preimage: Option<String>,
1877
1878 invoice: Option<String>,
1882
1883 bolt12_offer: Option<String>,
1884
1885 payment_hash: Option<String>,
1887
1888 destination_pubkey: Option<String>,
1890
1891 lnurl_info: Option<LnUrlInfo>,
1893
1894 bip353_address: Option<String>,
1896
1897 claim_tx_id: Option<String>,
1899
1900 refund_tx_id: Option<String>,
1902
1903 refund_tx_amount_sat: Option<u64>,
1905 },
1906 Liquid {
1908 destination: String,
1910
1911 description: String,
1913
1914 asset_id: String,
1916
1917 asset_info: Option<AssetInfo>,
1919
1920 lnurl_info: Option<LnUrlInfo>,
1922
1923 bip353_address: Option<String>,
1925 },
1926 Bitcoin {
1928 swap_id: 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 claim_tx_id: Option<String>,
1948
1949 refund_tx_id: Option<String>,
1951
1952 refund_tx_amount_sat: Option<u64>,
1954 },
1955}
1956
1957impl PaymentDetails {
1958 pub(crate) fn get_swap_id(&self) -> Option<String> {
1959 match self {
1960 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
1961 Some(swap_id.clone())
1962 }
1963 Self::Liquid { .. } => None,
1964 }
1965 }
1966
1967 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
1968 match self {
1969 Self::Lightning {
1970 refund_tx_amount_sat,
1971 ..
1972 }
1973 | Self::Bitcoin {
1974 refund_tx_amount_sat,
1975 ..
1976 } => *refund_tx_amount_sat,
1977 Self::Liquid { .. } => None,
1978 }
1979 }
1980
1981 pub(crate) fn get_description(&self) -> Option<String> {
1982 match self {
1983 Self::Lightning { description, .. }
1984 | Self::Bitcoin { description, .. }
1985 | Self::Liquid { description, .. } => Some(description.clone()),
1986 }
1987 }
1988
1989 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
1990 match self {
1991 Self::Liquid { asset_id, .. } => {
1992 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
1993 }
1994 _ => true,
1995 }
1996 }
1997}
1998
1999#[derive(Debug, Clone, PartialEq, Serialize)]
2003pub struct Payment {
2004 pub destination: Option<String>,
2007
2008 pub tx_id: Option<String>,
2009
2010 pub unblinding_data: Option<String>,
2013
2014 pub timestamp: u32,
2020
2021 pub amount_sat: u64,
2025
2026 pub fees_sat: u64,
2040
2041 pub swapper_fees_sat: Option<u64>,
2044
2045 pub payment_type: PaymentType,
2047
2048 pub status: PaymentState,
2054
2055 pub details: PaymentDetails,
2058}
2059impl Payment {
2060 pub(crate) fn from_pending_swap(
2061 swap: PaymentSwapData,
2062 payment_type: PaymentType,
2063 payment_details: PaymentDetails,
2064 ) -> Payment {
2065 let amount_sat = match payment_type {
2066 PaymentType::Receive => swap.receiver_amount_sat,
2067 PaymentType::Send => swap.payer_amount_sat,
2068 };
2069
2070 Payment {
2071 destination: swap.invoice.clone(),
2072 tx_id: None,
2073 unblinding_data: None,
2074 timestamp: swap.created_at,
2075 amount_sat,
2076 fees_sat: swap
2077 .payer_amount_sat
2078 .saturating_sub(swap.receiver_amount_sat),
2079 swapper_fees_sat: Some(swap.swapper_fees_sat),
2080 payment_type,
2081 status: swap.status,
2082 details: payment_details,
2083 }
2084 }
2085
2086 pub(crate) fn from_tx_data(
2087 tx: PaymentTxData,
2088 swap: Option<PaymentSwapData>,
2089 details: PaymentDetails,
2090 ) -> Payment {
2091 let (amount_sat, fees_sat) = match swap.as_ref() {
2092 Some(s) => match tx.payment_type {
2093 PaymentType::Receive => (tx.amount, s.payer_amount_sat.saturating_sub(tx.amount)),
2097 PaymentType::Send => (
2098 s.receiver_amount_sat,
2099 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2100 ),
2101 },
2102 None => {
2103 let (amount_sat, fees_sat) = match tx.payment_type {
2104 PaymentType::Receive => (tx.amount, 0),
2105 PaymentType::Send => (tx.amount, tx.fees_sat),
2106 };
2107 match details {
2110 PaymentDetails::Liquid {
2111 asset_info: Some(ref asset_info),
2112 ..
2113 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2114 _ => (amount_sat, fees_sat),
2115 }
2116 }
2117 };
2118 Payment {
2119 tx_id: Some(tx.tx_id),
2120 unblinding_data: tx.unblinding_data,
2121 destination: match &swap {
2125 Some(PaymentSwapData {
2126 swap_type: PaymentSwapType::Receive,
2127 invoice,
2128 ..
2129 }) => invoice.clone(),
2130 Some(PaymentSwapData {
2131 swap_type: PaymentSwapType::Send,
2132 invoice,
2133 bolt12_offer,
2134 ..
2135 }) => bolt12_offer.clone().or(invoice.clone()),
2136 Some(PaymentSwapData {
2137 swap_type: PaymentSwapType::Chain,
2138 claim_address,
2139 ..
2140 }) => claim_address.clone(),
2141 _ => match &details {
2142 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2143 _ => None,
2144 },
2145 },
2146 timestamp: tx
2147 .timestamp
2148 .or(swap.as_ref().map(|s| s.created_at))
2149 .unwrap_or(utils::now()),
2150 amount_sat,
2151 fees_sat,
2152 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2153 payment_type: tx.payment_type,
2154 status: match &swap {
2155 Some(swap) => swap.status,
2156 None => match tx.is_confirmed {
2157 true => PaymentState::Complete,
2158 false => PaymentState::Pending,
2159 },
2160 },
2161 details,
2162 }
2163 }
2164
2165 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2166 match self.details.clone() {
2167 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2168 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2169 PaymentDetails::Liquid { .. } => None,
2170 }
2171 .flatten()
2172 }
2173}
2174
2175#[derive(Deserialize, Serialize, Clone, Debug)]
2177#[serde(rename_all = "camelCase")]
2178pub struct RecommendedFees {
2179 pub fastest_fee: u64,
2180 pub half_hour_fee: u64,
2181 pub hour_fee: u64,
2182 pub economy_fee: u64,
2183 pub minimum_fee: u64,
2184}
2185
2186#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2188pub enum BuyBitcoinProvider {
2189 #[strum(serialize = "moonpay")]
2190 Moonpay,
2191}
2192
2193#[derive(Debug, Serialize)]
2195pub struct PrepareBuyBitcoinRequest {
2196 pub provider: BuyBitcoinProvider,
2197 pub amount_sat: u64,
2198}
2199
2200#[derive(Clone, Debug, Serialize)]
2202pub struct PrepareBuyBitcoinResponse {
2203 pub provider: BuyBitcoinProvider,
2204 pub amount_sat: u64,
2205 pub fees_sat: u64,
2206}
2207
2208#[derive(Clone, Debug, Serialize)]
2210pub struct BuyBitcoinRequest {
2211 pub prepare_response: PrepareBuyBitcoinResponse,
2212
2213 pub redirect_url: Option<String>,
2217}
2218
2219#[derive(Clone, Debug)]
2221pub struct LogEntry {
2222 pub line: String,
2223 pub level: String,
2224}
2225
2226#[derive(Clone, Debug, Serialize, Deserialize)]
2227struct InternalLeaf {
2228 pub output: String,
2229 pub version: u8,
2230}
2231impl From<InternalLeaf> for Leaf {
2232 fn from(value: InternalLeaf) -> Self {
2233 Leaf {
2234 output: value.output,
2235 version: value.version,
2236 }
2237 }
2238}
2239impl From<Leaf> for InternalLeaf {
2240 fn from(value: Leaf) -> Self {
2241 InternalLeaf {
2242 output: value.output,
2243 version: value.version,
2244 }
2245 }
2246}
2247
2248#[derive(Clone, Debug, Serialize, Deserialize)]
2249pub(super) struct InternalSwapTree {
2250 claim_leaf: InternalLeaf,
2251 refund_leaf: InternalLeaf,
2252}
2253impl From<InternalSwapTree> for SwapTree {
2254 fn from(value: InternalSwapTree) -> Self {
2255 SwapTree {
2256 claim_leaf: value.claim_leaf.into(),
2257 refund_leaf: value.refund_leaf.into(),
2258 }
2259 }
2260}
2261impl From<SwapTree> for InternalSwapTree {
2262 fn from(value: SwapTree) -> Self {
2263 InternalSwapTree {
2264 claim_leaf: value.claim_leaf.into(),
2265 refund_leaf: value.refund_leaf.into(),
2266 }
2267 }
2268}
2269
2270#[derive(Debug, Serialize)]
2272pub struct PrepareLnUrlPayRequest {
2273 pub data: LnUrlPayRequestData,
2275 pub amount: PayAmount,
2277 pub bip353_address: Option<String>,
2280 pub comment: Option<String>,
2282 pub validate_success_action_url: Option<bool>,
2285}
2286
2287#[derive(Debug, Serialize)]
2289pub struct PrepareLnUrlPayResponse {
2290 pub destination: SendDestination,
2292 pub fees_sat: u64,
2294 pub data: LnUrlPayRequestData,
2296 pub comment: Option<String>,
2298 pub success_action: Option<SuccessAction>,
2301}
2302
2303#[derive(Debug, Serialize)]
2305pub struct LnUrlPayRequest {
2306 pub prepare_response: PrepareLnUrlPayResponse,
2308}
2309
2310#[derive(Serialize)]
2322#[allow(clippy::large_enum_variant)]
2323pub enum LnUrlPayResult {
2324 EndpointSuccess { data: LnUrlPaySuccessData },
2325 EndpointError { data: LnUrlErrorData },
2326 PayError { data: LnUrlPayErrorData },
2327}
2328
2329#[derive(Serialize)]
2330pub struct LnUrlPaySuccessData {
2331 pub payment: Payment,
2332 pub success_action: Option<SuccessActionProcessed>,
2333}
2334
2335#[derive(Debug, Clone)]
2336pub enum Transaction {
2337 Liquid(boltz_client::elements::Transaction),
2338 Bitcoin(boltz_client::bitcoin::Transaction),
2339}
2340
2341impl Transaction {
2342 pub(crate) fn txid(&self) -> String {
2343 match self {
2344 Transaction::Liquid(tx) => tx.txid().to_hex(),
2345 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2346 }
2347 }
2348}
2349
2350#[derive(Debug, Clone)]
2351pub enum Utxo {
2352 Liquid(
2353 Box<(
2354 boltz_client::elements::OutPoint,
2355 boltz_client::elements::TxOut,
2356 )>,
2357 ),
2358 Bitcoin(
2359 (
2360 boltz_client::bitcoin::OutPoint,
2361 boltz_client::bitcoin::TxOut,
2362 ),
2363 ),
2364}
2365
2366impl Utxo {
2367 pub(crate) fn as_bitcoin(
2368 &self,
2369 ) -> Option<&(
2370 boltz_client::bitcoin::OutPoint,
2371 boltz_client::bitcoin::TxOut,
2372 )> {
2373 match self {
2374 Utxo::Liquid(_) => None,
2375 Utxo::Bitcoin(utxo) => Some(utxo),
2376 }
2377 }
2378
2379 pub(crate) fn as_liquid(
2380 &self,
2381 ) -> Option<
2382 Box<(
2383 boltz_client::elements::OutPoint,
2384 boltz_client::elements::TxOut,
2385 )>,
2386 > {
2387 match self {
2388 Utxo::Bitcoin(_) => None,
2389 Utxo::Liquid(utxo) => Some(utxo.clone()),
2390 }
2391 }
2392}
2393
2394#[derive(Debug, Clone)]
2396pub struct FetchPaymentProposedFeesRequest {
2397 pub swap_id: String,
2398}
2399
2400#[derive(Debug, Clone, Serialize)]
2402pub struct FetchPaymentProposedFeesResponse {
2403 pub swap_id: String,
2404 pub fees_sat: u64,
2405 pub payer_amount_sat: u64,
2407 pub receiver_amount_sat: u64,
2409}
2410
2411#[derive(Debug, Clone)]
2413pub struct AcceptPaymentProposedFeesRequest {
2414 pub response: FetchPaymentProposedFeesResponse,
2415}
2416
2417#[derive(Clone, Debug)]
2418pub struct History<T> {
2419 pub txid: T,
2420 pub height: i32,
2425}
2426pub(crate) type LBtcHistory = History<elements::Txid>;
2427pub(crate) type BtcHistory = History<bitcoin::Txid>;
2428
2429impl<T> History<T> {
2430 pub(crate) fn confirmed(&self) -> bool {
2431 self.height > 0
2432 }
2433}
2434#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2435impl From<electrum_client::GetHistoryRes> for BtcHistory {
2436 fn from(value: electrum_client::GetHistoryRes) -> Self {
2437 Self {
2438 txid: value.tx_hash,
2439 height: value.height,
2440 }
2441 }
2442}
2443impl From<lwk_wollet::History> for LBtcHistory {
2444 fn from(value: lwk_wollet::History) -> Self {
2445 Self::from(&value)
2446 }
2447}
2448impl From<&lwk_wollet::History> for LBtcHistory {
2449 fn from(value: &lwk_wollet::History) -> Self {
2450 Self {
2451 txid: value.txid,
2452 height: value.height,
2453 }
2454 }
2455}
2456pub(crate) type BtcScript = bitcoin::ScriptBuf;
2457pub(crate) type LBtcScript = elements::Script;
2458
2459#[derive(Clone, Debug)]
2460pub struct BtcScriptBalance {
2461 pub confirmed: u64,
2463 pub unconfirmed: i64,
2467}
2468#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2469impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2470 fn from(val: electrum_client::GetBalanceRes) -> Self {
2471 Self {
2472 confirmed: val.confirmed,
2473 unconfirmed: val.unconfirmed,
2474 }
2475 }
2476}
2477
2478#[macro_export]
2479macro_rules! get_updated_fields {
2480 ($($var:ident),* $(,)?) => {{
2481 let mut options = Vec::new();
2482 $(
2483 if $var.is_some() {
2484 options.push(stringify!($var).to_string());
2485 }
2486 )*
2487 match options.len() > 0 {
2488 true => Some(options),
2489 false => None,
2490 }
2491 }};
2492}