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