1use anyhow::{anyhow, bail, Result};
2use bitcoin::{bip32, ScriptBuf};
3use boltz_client::{
4 boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2},
5 network::{BitcoinChain, Chain, LiquidChain},
6 swaps::boltz::{
7 CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
8 },
9 BtcSwapScript, Keypair, LBtcSwapScript,
10};
11use derivative::Derivative;
12use elements::AssetId;
13use lwk_wollet::ElementsNetwork;
14use maybe_sync::{MaybeSend, MaybeSync};
15use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
16use rusqlite::ToSql;
17use sdk_common::prelude::*;
18use sdk_common::utils::Arc;
19use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
20use serde::{Deserialize, Serialize};
21use std::cmp::PartialEq;
22use std::path::PathBuf;
23use std::str::FromStr;
24use strum_macros::{Display, EnumString};
25
26use crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT;
27use crate::utils;
28use crate::{
29 bitcoin,
30 chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
31 elements,
32 error::{PaymentError, SdkError, SdkResult},
33};
34use crate::{
35 chain::bitcoin::esplora::EsploraBitcoinChainService,
36 chain::liquid::esplora::EsploraLiquidChainService, prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
37};
38
39pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
41pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
42pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
43pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api";
44pub const BREEZ_SWAP_PROXY_URL: &str = "https://swap.breez.technology/v2";
45pub const DEFAULT_ONCHAIN_FEE_RATE_LEEWAY_SAT: u64 = 500;
46
47const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
48
49#[derive(Clone, Debug, Serialize)]
50pub enum BlockchainExplorer {
51 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
52 Electrum { url: String },
53 Esplora {
54 url: String,
55 use_waterfalls: bool,
57 },
58}
59
60#[derive(Clone, Debug, Serialize)]
62pub struct Config {
63 pub liquid_explorer: BlockchainExplorer,
64 pub bitcoin_explorer: BlockchainExplorer,
65 pub working_dir: 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: Option<u64>,
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 network: LiquidNetwork::Mainnet,
116 payment_timeout_sec: 15,
117 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
118 zero_conf_max_amount_sat: None,
119 breez_api_key,
120 external_input_parsers: None,
121 use_default_external_input_parsers: true,
122 onchain_fee_rate_leeway_sat: None,
123 asset_metadata: None,
124 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
125 }
126 }
127
128 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
129 Config {
130 liquid_explorer: BlockchainExplorer::Esplora {
131 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
132 use_waterfalls: true,
133 },
134 bitcoin_explorer: BlockchainExplorer::Esplora {
135 url: "https://blockstream.info/api/".to_string(),
136 use_waterfalls: false,
137 },
138 working_dir: ".".to_string(),
139 network: LiquidNetwork::Mainnet,
140 payment_timeout_sec: 15,
141 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
142 zero_conf_max_amount_sat: None,
143 breez_api_key,
144 external_input_parsers: None,
145 use_default_external_input_parsers: true,
146 onchain_fee_rate_leeway_sat: None,
147 asset_metadata: None,
148 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
149 }
150 }
151
152 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
153 pub fn testnet(breez_api_key: Option<String>) -> Self {
154 Config {
155 liquid_explorer: BlockchainExplorer::Electrum {
156 url: "elements-testnet.blockstream.info:50002".to_string(),
157 },
158 bitcoin_explorer: BlockchainExplorer::Electrum {
159 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
160 },
161 working_dir: ".".to_string(),
162 network: LiquidNetwork::Testnet,
163 payment_timeout_sec: 15,
164 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
165 zero_conf_max_amount_sat: None,
166 breez_api_key,
167 external_input_parsers: None,
168 use_default_external_input_parsers: true,
169 onchain_fee_rate_leeway_sat: None,
170 asset_metadata: None,
171 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
172 }
173 }
174
175 pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
176 Config {
177 liquid_explorer: BlockchainExplorer::Esplora {
178 url: "https://blockstream.info/liquidtestnet/api".to_string(),
179 use_waterfalls: false,
180 },
181 bitcoin_explorer: BlockchainExplorer::Esplora {
182 url: "https://blockstream.info/testnet/api/".to_string(),
183 use_waterfalls: false,
184 },
185 working_dir: ".".to_string(),
186 network: LiquidNetwork::Testnet,
187 payment_timeout_sec: 15,
188 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
189 zero_conf_max_amount_sat: None,
190 breez_api_key,
191 external_input_parsers: None,
192 use_default_external_input_parsers: true,
193 onchain_fee_rate_leeway_sat: None,
194 asset_metadata: None,
195 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
196 }
197 }
198
199 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
200 pub fn regtest() -> Self {
201 Config {
202 liquid_explorer: BlockchainExplorer::Electrum {
203 url: "localhost:19002".to_string(),
204 },
205 bitcoin_explorer: BlockchainExplorer::Electrum {
206 url: "localhost:19001".to_string(),
207 },
208 working_dir: ".".to_string(),
209 network: LiquidNetwork::Regtest,
210 payment_timeout_sec: 15,
211 sync_service_url: Some("http://localhost:8088".to_string()),
212 zero_conf_max_amount_sat: None,
213 breez_api_key: None,
214 external_input_parsers: None,
215 use_default_external_input_parsers: true,
216 onchain_fee_rate_leeway_sat: None,
217 asset_metadata: None,
218 sideswap_api_key: None,
219 }
220 }
221
222 pub fn regtest_esplora() -> Self {
223 Config {
224 liquid_explorer: BlockchainExplorer::Esplora {
225 url: "http://localhost:3120/api".to_string(),
226 use_waterfalls: true,
227 },
228 bitcoin_explorer: BlockchainExplorer::Esplora {
229 url: "http://localhost:4002/api".to_string(),
230 use_waterfalls: false,
231 },
232 working_dir: ".".to_string(),
233 network: LiquidNetwork::Regtest,
234 payment_timeout_sec: 15,
235 sync_service_url: Some("http://localhost:8089".to_string()),
236 zero_conf_max_amount_sat: None,
237 breez_api_key: None,
238 external_input_parsers: None,
239 use_default_external_input_parsers: true,
240 onchain_fee_rate_leeway_sat: None,
241 asset_metadata: None,
242 sideswap_api_key: None,
243 }
244 }
245
246 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
247 Ok(PathBuf::from(base_dir)
248 .join(match self.network {
249 LiquidNetwork::Mainnet => "mainnet",
250 LiquidNetwork::Testnet => "testnet",
251 LiquidNetwork::Regtest => "regtest",
252 })
253 .join(fingerprint_hex)
254 .to_str()
255 .ok_or(anyhow::anyhow!(
256 "Could not get retrieve current wallet directory"
257 ))?
258 .to_string())
259 }
260
261 pub fn zero_conf_max_amount_sat(&self) -> u64 {
262 self.zero_conf_max_amount_sat
263 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
264 }
265
266 pub(crate) fn lbtc_asset_id(&self) -> String {
267 utils::lbtc_asset_id(self.network).to_string()
268 }
269
270 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
271 let mut external_input_parsers = Vec::new();
272 if self.use_default_external_input_parsers {
273 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
274 .iter()
275 .map(|(id, regex, url)| ExternalInputParser {
276 provider_id: id.to_string(),
277 input_regex: regex.to_string(),
278 parser_url: url.to_string(),
279 })
280 .collect::<Vec<_>>();
281 external_input_parsers.extend(default_parsers);
282 }
283 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
284
285 external_input_parsers
286 }
287
288 pub(crate) fn default_boltz_url(&self) -> &str {
289 match self.network {
290 LiquidNetwork::Mainnet => {
291 if self.breez_api_key.is_some() {
292 BREEZ_SWAP_PROXY_URL
293 } else {
294 BOLTZ_MAINNET_URL_V2
295 }
296 }
297 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
298 LiquidNetwork::Regtest => "http://localhost:8387/v2",
300 }
301 }
302
303 pub fn sync_enabled(&self) -> bool {
304 self.sync_service_url.is_some()
305 }
306
307 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
308 match self.bitcoin_explorer {
309 BlockchainExplorer::Esplora { .. } => {
310 Arc::new(EsploraBitcoinChainService::new(self.clone()))
311 }
312 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
313 BlockchainExplorer::Electrum { .. } => Arc::new(
314 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
315 ),
316 }
317 }
318
319 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
320 match &self.liquid_explorer {
321 BlockchainExplorer::Esplora { url, .. } => {
322 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
323 bail!("Cannot start the Breez Esplora chain service without providing an API key. See https://sdk-doc-liquid.breez.technology/guide/getting_started.html#api-key")
324 }
325 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
326 }
327 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
328 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
329 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
330 )),
331 }
332 }
333
334 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
335 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
336 match self.network {
337 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
338 LiquidNetwork::Regtest => (false, false),
339 }
340 }
341
342 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
343 pub(crate) fn electrum_client(
344 &self,
345 url: &str,
346 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
347 let (tls, validate_domain) = self.electrum_tls_options();
348 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
349 lwk_wollet::ElectrumClient::with_options(
350 &electrum_url,
351 lwk_wollet::ElectrumOptions { timeout: Some(3) },
352 )
353 }
354}
355
356#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
359pub enum LiquidNetwork {
360 Mainnet,
362 Testnet,
364 Regtest,
366}
367impl LiquidNetwork {
368 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
369 match self {
370 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
371 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
372 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
373 }
374 }
375}
376
377impl From<LiquidNetwork> for ElementsNetwork {
378 fn from(value: LiquidNetwork) -> Self {
379 match value {
380 LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
381 LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
382 LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
383 policy_asset: AssetId::from_str(
384 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
385 )
386 .unwrap(),
387 },
388 }
389 }
390}
391
392impl From<LiquidNetwork> for Chain {
393 fn from(value: LiquidNetwork) -> Self {
394 Chain::Liquid(value.into())
395 }
396}
397
398impl From<LiquidNetwork> for LiquidChain {
399 fn from(value: LiquidNetwork) -> Self {
400 match value {
401 LiquidNetwork::Mainnet => LiquidChain::Liquid,
402 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
403 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
404 }
405 }
406}
407
408impl TryFrom<&str> for LiquidNetwork {
409 type Error = anyhow::Error;
410
411 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
412 match value.to_lowercase().as_str() {
413 "mainnet" => Ok(LiquidNetwork::Mainnet),
414 "testnet" => Ok(LiquidNetwork::Testnet),
415 "regtest" => Ok(LiquidNetwork::Regtest),
416 _ => Err(anyhow!("Invalid network")),
417 }
418 }
419}
420
421impl From<LiquidNetwork> for Network {
422 fn from(value: LiquidNetwork) -> Self {
423 match value {
424 LiquidNetwork::Mainnet => Self::Bitcoin,
425 LiquidNetwork::Testnet => Self::Testnet,
426 LiquidNetwork::Regtest => Self::Regtest,
427 }
428 }
429}
430
431impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
432 fn from(value: LiquidNetwork) -> Self {
433 match value {
434 LiquidNetwork::Mainnet => Self::Bitcoin,
435 LiquidNetwork::Testnet => Self::Testnet,
436 LiquidNetwork::Regtest => Self::Regtest,
437 }
438 }
439}
440
441impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
442 fn from(value: LiquidNetwork) -> Self {
443 match value {
444 LiquidNetwork::Mainnet => Self::Bitcoin,
445 LiquidNetwork::Testnet => Self::Testnet,
446 LiquidNetwork::Regtest => Self::Regtest,
447 }
448 }
449}
450
451pub trait EventListener: MaybeSend + MaybeSync {
453 fn on_event(&self, e: SdkEvent);
454}
455
456#[derive(Clone, Debug, PartialEq)]
459pub enum SdkEvent {
460 PaymentFailed {
461 details: Payment,
462 },
463 PaymentPending {
464 details: Payment,
465 },
466 PaymentRefundable {
467 details: Payment,
468 },
469 PaymentRefunded {
470 details: Payment,
471 },
472 PaymentRefundPending {
473 details: Payment,
474 },
475 PaymentSucceeded {
476 details: Payment,
477 },
478 PaymentWaitingConfirmation {
479 details: Payment,
480 },
481 PaymentWaitingFeeAcceptance {
482 details: Payment,
483 },
484 Synced,
486 DataSynced {
488 did_pull_new_records: bool,
490 },
491}
492
493#[derive(thiserror::Error, Debug)]
494pub enum SignerError {
495 #[error("Signer error: {err}")]
496 Generic { err: String },
497}
498
499impl From<anyhow::Error> for SignerError {
500 fn from(err: anyhow::Error) -> Self {
501 SignerError::Generic {
502 err: err.to_string(),
503 }
504 }
505}
506
507impl From<bip32::Error> for SignerError {
508 fn from(err: bip32::Error) -> Self {
509 SignerError::Generic {
510 err: err.to_string(),
511 }
512 }
513}
514
515pub trait Signer: MaybeSend + MaybeSync {
518 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
521
522 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
528
529 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
531
532 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
534
535 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
537
538 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
541
542 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
544
545 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
547}
548
549pub struct ConnectRequest {
552 pub config: Config,
554 pub mnemonic: Option<String>,
556 pub passphrase: Option<String>,
558 pub seed: Option<Vec<u8>>,
560}
561
562pub struct ConnectWithSignerRequest {
563 pub config: Config,
564}
565
566#[derive(Clone, Debug)]
569pub(crate) struct ReservedAddress {
570 pub(crate) address: String,
572 pub(crate) expiry_block_height: u32,
574}
575
576#[derive(Clone, Debug, Serialize)]
578pub enum PaymentMethod {
579 #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
580 Lightning,
581 Bolt11Invoice,
582 Bolt12Offer,
583 BitcoinAddress,
584 LiquidAddress,
585}
586
587#[derive(Debug, Serialize, Clone)]
588pub enum ReceiveAmount {
589 Bitcoin { payer_amount_sat: u64 },
591
592 Asset {
594 asset_id: String,
595 payer_amount: Option<f64>,
596 },
597}
598
599#[derive(Debug, Serialize)]
601pub struct PrepareReceiveRequest {
602 pub payment_method: PaymentMethod,
603 pub amount: Option<ReceiveAmount>,
605}
606
607#[derive(Debug, Serialize, Clone)]
609pub struct PrepareReceiveResponse {
610 pub payment_method: PaymentMethod,
611 pub fees_sat: u64,
620 pub amount: Option<ReceiveAmount>,
622 pub min_payer_amount_sat: Option<u64>,
626 pub max_payer_amount_sat: Option<u64>,
630 pub swapper_feerate: Option<f64>,
634}
635
636#[derive(Debug, Serialize)]
638pub struct ReceivePaymentRequest {
639 pub prepare_response: PrepareReceiveResponse,
640 pub description: Option<String>,
642 pub use_description_hash: Option<bool>,
644 pub payer_note: Option<String>,
646}
647
648#[derive(Debug, Serialize)]
650pub struct ReceivePaymentResponse {
651 pub destination: String,
654}
655
656#[derive(Debug, Serialize)]
658pub struct CreateBolt12InvoiceRequest {
659 pub offer: String,
661 pub invoice_request: String,
663}
664
665#[derive(Debug, Serialize, Clone)]
667pub struct CreateBolt12InvoiceResponse {
668 pub invoice: String,
670}
671
672#[derive(Debug, Serialize)]
674pub struct Limits {
675 pub min_sat: u64,
676 pub max_sat: u64,
677 pub max_zero_conf_sat: u64,
678}
679
680#[derive(Debug, Serialize)]
682pub struct LightningPaymentLimitsResponse {
683 pub send: Limits,
685 pub receive: Limits,
687}
688
689#[derive(Debug, Serialize)]
691pub struct OnchainPaymentLimitsResponse {
692 pub send: Limits,
694 pub receive: Limits,
696}
697
698#[derive(Debug, Serialize, Clone)]
700pub struct PrepareSendRequest {
701 pub destination: String,
704 pub amount: Option<PayAmount>,
707}
708
709#[derive(Clone, Debug, Serialize)]
711pub enum SendDestination {
712 LiquidAddress {
713 address_data: liquid::LiquidAddressData,
714 bip353_address: Option<String>,
716 },
717 Bolt11 {
718 invoice: LNInvoice,
719 bip353_address: Option<String>,
721 },
722 Bolt12 {
723 offer: LNOffer,
724 receiver_amount_sat: u64,
725 bip353_address: Option<String>,
727 },
728}
729
730#[derive(Debug, Serialize, Clone)]
732pub struct PrepareSendResponse {
733 pub destination: SendDestination,
734 pub amount: Option<PayAmount>,
736 pub fees_sat: Option<u64>,
739 pub estimated_asset_fees: Option<f64>,
743}
744
745#[derive(Debug, Serialize)]
747pub struct SendPaymentRequest {
748 pub prepare_response: PrepareSendResponse,
749 pub use_asset_fees: Option<bool>,
751 pub payer_note: Option<String>,
753}
754
755#[derive(Debug, Serialize)]
757pub struct SendPaymentResponse {
758 pub payment: Payment,
759}
760
761pub(crate) struct SendPaymentViaSwapRequest {
762 pub(crate) invoice: String,
763 pub(crate) bolt12_offer: Option<String>,
764 pub(crate) payment_hash: String,
765 pub(crate) description: Option<String>,
766 pub(crate) receiver_amount_sat: u64,
767 pub(crate) fees_sat: u64,
768}
769
770#[derive(Debug, Serialize, Clone)]
772pub enum PayAmount {
773 Bitcoin { receiver_amount_sat: u64 },
775
776 Asset {
778 asset_id: String,
779 receiver_amount: f64,
780 estimate_asset_fees: Option<bool>,
781 },
782
783 Drain,
785}
786
787#[derive(Debug, Serialize, Clone)]
789pub struct PreparePayOnchainRequest {
790 pub amount: PayAmount,
792 pub fee_rate_sat_per_vbyte: Option<u32>,
794}
795
796#[derive(Debug, Serialize, Clone)]
798pub struct PreparePayOnchainResponse {
799 pub receiver_amount_sat: u64,
800 pub claim_fees_sat: u64,
801 pub total_fees_sat: u64,
802}
803
804#[derive(Debug, Serialize)]
806pub struct PayOnchainRequest {
807 pub address: String,
808 pub prepare_response: PreparePayOnchainResponse,
809}
810
811#[derive(Debug, Serialize)]
813pub struct PrepareRefundRequest {
814 pub swap_address: String,
816 pub refund_address: String,
818 pub fee_rate_sat_per_vbyte: u32,
820}
821
822#[derive(Debug, Serialize)]
824pub struct PrepareRefundResponse {
825 pub tx_vsize: u32,
826 pub tx_fee_sat: u64,
827 pub last_refund_tx_id: Option<String>,
829}
830
831#[derive(Debug, Serialize)]
833pub struct RefundRequest {
834 pub swap_address: String,
836 pub refund_address: String,
838 pub fee_rate_sat_per_vbyte: u32,
840}
841
842#[derive(Debug, Serialize)]
844pub struct RefundResponse {
845 pub refund_tx_id: String,
846}
847
848#[derive(Clone, Debug, Default, Serialize, Deserialize)]
850pub struct AssetBalance {
851 pub asset_id: String,
852 pub balance_sat: u64,
853 pub name: Option<String>,
854 pub ticker: Option<String>,
855 pub balance: Option<f64>,
856}
857
858#[derive(Debug, Serialize, Deserialize, Default)]
859pub struct BlockchainInfo {
860 pub liquid_tip: u32,
861 pub bitcoin_tip: u32,
862}
863
864#[derive(Copy, Clone)]
865pub(crate) struct ChainTips {
866 pub liquid_tip: u32,
867 pub bitcoin_tip: u32,
868}
869
870#[derive(Debug, Serialize, Deserialize)]
871pub struct WalletInfo {
872 pub balance_sat: u64,
874 pub pending_send_sat: u64,
876 pub pending_receive_sat: u64,
878 pub fingerprint: String,
880 pub pubkey: String,
882 #[serde(default)]
884 pub asset_balances: Vec<AssetBalance>,
885}
886
887impl WalletInfo {
888 pub(crate) fn validate_sufficient_funds(
889 &self,
890 network: LiquidNetwork,
891 amount_sat: u64,
892 fees_sat: Option<u64>,
893 asset_id: &str,
894 ) -> Result<(), PaymentError> {
895 let fees_sat = fees_sat.unwrap_or(0);
896 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
897 ensure_sdk!(
898 amount_sat + fees_sat <= self.balance_sat,
899 PaymentError::InsufficientFunds
900 );
901 } else {
902 match self
903 .asset_balances
904 .iter()
905 .find(|ab| ab.asset_id.eq(asset_id))
906 {
907 Some(asset_balance) => ensure_sdk!(
908 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
909 PaymentError::InsufficientFunds
910 ),
911 None => return Err(PaymentError::InsufficientFunds),
912 }
913 }
914 Ok(())
915 }
916}
917
918#[derive(Debug, Serialize, Deserialize)]
920pub struct GetInfoResponse {
921 pub wallet_info: WalletInfo,
923 #[serde(default)]
925 pub blockchain_info: BlockchainInfo,
926}
927
928#[derive(Clone, Debug, PartialEq)]
930pub struct SignMessageRequest {
931 pub message: String,
932}
933
934#[derive(Clone, Debug, PartialEq)]
936pub struct SignMessageResponse {
937 pub signature: String,
938}
939
940#[derive(Clone, Debug, PartialEq)]
942pub struct CheckMessageRequest {
943 pub message: String,
945 pub pubkey: String,
947 pub signature: String,
949}
950
951#[derive(Clone, Debug, PartialEq)]
953pub struct CheckMessageResponse {
954 pub is_valid: bool,
957}
958
959#[derive(Debug, Serialize)]
961pub struct BackupRequest {
962 pub backup_path: Option<String>,
969}
970
971#[derive(Debug, Serialize)]
973pub struct RestoreRequest {
974 pub backup_path: Option<String>,
975}
976
977#[derive(Default)]
979pub struct ListPaymentsRequest {
980 pub filters: Option<Vec<PaymentType>>,
981 pub states: Option<Vec<PaymentState>>,
982 pub from_timestamp: Option<i64>,
984 pub to_timestamp: Option<i64>,
986 pub offset: Option<u32>,
987 pub limit: Option<u32>,
988 pub details: Option<ListPaymentDetails>,
989 pub sort_ascending: Option<bool>,
990}
991
992#[derive(Debug, Serialize)]
994pub enum ListPaymentDetails {
995 Liquid {
997 asset_id: Option<String>,
999 destination: Option<String>,
1001 },
1002
1003 Bitcoin {
1005 address: Option<String>,
1007 },
1008}
1009
1010#[derive(Debug, Serialize)]
1012pub enum GetPaymentRequest {
1013 PaymentHash { payment_hash: String },
1015 SwapId { swap_id: String },
1017}
1018
1019#[sdk_macros::async_trait]
1021pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1022 async fn on_bitcoin_block(&self, height: u32);
1023 async fn on_liquid_block(&self, height: u32);
1024}
1025
1026#[derive(Clone, Debug)]
1028pub enum Swap {
1029 Chain(ChainSwap),
1030 Send(SendSwap),
1031 Receive(ReceiveSwap),
1032}
1033impl Swap {
1034 pub(crate) fn id(&self) -> String {
1035 match &self {
1036 Swap::Chain(ChainSwap { id, .. })
1037 | Swap::Send(SendSwap { id, .. })
1038 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1039 }
1040 }
1041
1042 pub(crate) fn version(&self) -> u64 {
1043 match self {
1044 Swap::Chain(ChainSwap { metadata, .. })
1045 | Swap::Send(SendSwap { metadata, .. })
1046 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1047 }
1048 }
1049
1050 pub(crate) fn set_version(&mut self, version: u64) {
1051 match self {
1052 Swap::Chain(chain_swap) => {
1053 chain_swap.metadata.version = version;
1054 }
1055 Swap::Send(send_swap) => {
1056 send_swap.metadata.version = version;
1057 }
1058 Swap::Receive(receive_swap) => {
1059 receive_swap.metadata.version = version;
1060 }
1061 }
1062 }
1063
1064 pub(crate) fn last_updated_at(&self) -> u32 {
1065 match self {
1066 Swap::Chain(ChainSwap { metadata, .. })
1067 | Swap::Send(SendSwap { metadata, .. })
1068 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1069 }
1070 }
1071}
1072impl From<ChainSwap> for Swap {
1073 fn from(swap: ChainSwap) -> Self {
1074 Self::Chain(swap)
1075 }
1076}
1077impl From<SendSwap> for Swap {
1078 fn from(swap: SendSwap) -> Self {
1079 Self::Send(swap)
1080 }
1081}
1082impl From<ReceiveSwap> for Swap {
1083 fn from(swap: ReceiveSwap) -> Self {
1084 Self::Receive(swap)
1085 }
1086}
1087
1088#[derive(Clone, Debug)]
1089pub(crate) enum SwapScriptV2 {
1090 Bitcoin(BtcSwapScript),
1091 Liquid(LBtcSwapScript),
1092}
1093impl SwapScriptV2 {
1094 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1095 match self {
1096 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1097 _ => Err(anyhow!("Invalid chain")),
1098 }
1099 }
1100
1101 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1102 match self {
1103 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1104 _ => Err(anyhow!("Invalid chain")),
1105 }
1106 }
1107}
1108
1109#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1110pub enum Direction {
1111 Incoming = 0,
1112 Outgoing = 1,
1113}
1114impl ToSql for Direction {
1115 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1116 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1117 }
1118}
1119impl FromSql for Direction {
1120 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1121 match value {
1122 ValueRef::Integer(i) => match i as u8 {
1123 0 => Ok(Direction::Incoming),
1124 1 => Ok(Direction::Outgoing),
1125 _ => Err(FromSqlError::OutOfRange(i)),
1126 },
1127 _ => Err(FromSqlError::InvalidType),
1128 }
1129 }
1130}
1131
1132#[derive(Clone, Debug, Default)]
1133pub(crate) struct SwapMetadata {
1134 pub(crate) version: u64,
1136 pub(crate) last_updated_at: u32,
1137 pub(crate) is_local: bool,
1138}
1139
1140#[derive(Clone, Debug, Derivative)]
1144#[derivative(PartialEq)]
1145pub struct ChainSwap {
1146 pub(crate) id: String,
1147 pub(crate) direction: Direction,
1148 pub(crate) claim_address: Option<String>,
1151 pub(crate) lockup_address: String,
1152 pub(crate) refund_address: Option<String>,
1154 pub(crate) timeout_block_height: u32,
1155 pub(crate) preimage: String,
1156 pub(crate) description: Option<String>,
1157 pub(crate) payer_amount_sat: u64,
1159 pub(crate) actual_payer_amount_sat: Option<u64>,
1162 pub(crate) receiver_amount_sat: u64,
1164 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1166 pub(crate) claim_fees_sat: u64,
1167 pub(crate) pair_fees_json: String,
1169 pub(crate) accept_zero_conf: bool,
1170 pub(crate) create_response_json: String,
1172 pub(crate) server_lockup_tx_id: Option<String>,
1174 pub(crate) user_lockup_tx_id: Option<String>,
1176 pub(crate) claim_tx_id: Option<String>,
1178 pub(crate) refund_tx_id: Option<String>,
1180 pub(crate) created_at: u32,
1181 pub(crate) state: PaymentState,
1182 pub(crate) claim_private_key: String,
1183 pub(crate) refund_private_key: String,
1184 pub(crate) auto_accepted_fees: bool,
1185 #[derivative(PartialEq = "ignore")]
1187 pub(crate) metadata: SwapMetadata,
1188}
1189impl ChainSwap {
1190 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1191 utils::decode_keypair(&self.claim_private_key)
1192 }
1193
1194 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1195 utils::decode_keypair(&self.refund_private_key)
1196 }
1197
1198 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1199 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1200 serde_json::from_str(&self.create_response_json).map_err(|e| {
1201 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1202 })?;
1203
1204 Ok(CreateChainResponse {
1205 id: self.id.clone(),
1206 claim_details: internal_create_response.claim_details,
1207 lockup_details: internal_create_response.lockup_details,
1208 })
1209 }
1210
1211 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1212 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1213 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1214
1215 Ok(pair)
1216 }
1217
1218 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1219 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1220 let our_pubkey = self.get_claim_keypair()?.public_key();
1221 let swap_script = match self.direction {
1222 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1223 Side::Claim,
1224 chain_swap_details,
1225 our_pubkey.into(),
1226 )?),
1227 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1228 Side::Claim,
1229 chain_swap_details,
1230 our_pubkey.into(),
1231 )?),
1232 };
1233 Ok(swap_script)
1234 }
1235
1236 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1237 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1238 let our_pubkey = self.get_refund_keypair()?.public_key();
1239 let swap_script = match self.direction {
1240 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1241 Side::Lockup,
1242 chain_swap_details,
1243 our_pubkey.into(),
1244 )?),
1245 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1246 Side::Lockup,
1247 chain_swap_details,
1248 our_pubkey.into(),
1249 )?),
1250 };
1251 Ok(swap_script)
1252 }
1253
1254 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1256 &self,
1257 network: LiquidNetwork,
1258 ) -> SdkResult<ScriptBuf> {
1259 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1260 let script_pubkey = swap_script
1261 .to_address(network.as_bitcoin_chain())
1262 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1263 .script_pubkey();
1264 Ok(script_pubkey)
1265 }
1266
1267 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1268 RefundableSwap {
1269 swap_address: self.lockup_address.clone(),
1270 timestamp: self.created_at,
1271 amount_sat,
1272 last_refund_tx_id: self.refund_tx_id.clone(),
1273 }
1274 }
1275
1276 pub(crate) fn from_boltz_struct_to_json(
1277 create_response: &CreateChainResponse,
1278 expected_swap_id: &str,
1279 ) -> Result<String, PaymentError> {
1280 let internal_create_response =
1281 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1282 create_response,
1283 expected_swap_id,
1284 )?;
1285
1286 let create_response_json =
1287 serde_json::to_string(&internal_create_response).map_err(|e| {
1288 PaymentError::Generic {
1289 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1290 }
1291 })?;
1292
1293 Ok(create_response_json)
1294 }
1295
1296 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1297 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1298 }
1299}
1300
1301#[derive(Clone, Debug, Default)]
1302pub(crate) struct ChainSwapUpdate {
1303 pub(crate) swap_id: String,
1304 pub(crate) to_state: PaymentState,
1305 pub(crate) server_lockup_tx_id: Option<String>,
1306 pub(crate) user_lockup_tx_id: Option<String>,
1307 pub(crate) claim_address: Option<String>,
1308 pub(crate) claim_tx_id: Option<String>,
1309 pub(crate) refund_tx_id: Option<String>,
1310}
1311
1312#[derive(Clone, Debug, Derivative)]
1314#[derivative(PartialEq)]
1315pub struct SendSwap {
1316 pub(crate) id: String,
1317 pub(crate) invoice: String,
1319 pub(crate) bolt12_offer: Option<String>,
1321 pub(crate) payment_hash: Option<String>,
1322 pub(crate) destination_pubkey: Option<String>,
1323 pub(crate) description: Option<String>,
1324 pub(crate) preimage: Option<String>,
1325 pub(crate) payer_amount_sat: u64,
1326 pub(crate) receiver_amount_sat: u64,
1327 pub(crate) pair_fees_json: String,
1329 pub(crate) create_response_json: String,
1331 pub(crate) lockup_tx_id: Option<String>,
1333 pub(crate) refund_address: Option<String>,
1335 pub(crate) refund_tx_id: Option<String>,
1337 pub(crate) created_at: u32,
1338 pub(crate) timeout_block_height: u64,
1339 pub(crate) state: PaymentState,
1340 pub(crate) refund_private_key: String,
1341 #[derivative(PartialEq = "ignore")]
1343 pub(crate) metadata: SwapMetadata,
1344}
1345impl SendSwap {
1346 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1347 utils::decode_keypair(&self.refund_private_key)
1348 }
1349
1350 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1351 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1352 serde_json::from_str(&self.create_response_json).map_err(|e| {
1353 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1354 })?;
1355
1356 let res = CreateSubmarineResponse {
1357 id: self.id.clone(),
1358 accept_zero_conf: internal_create_response.accept_zero_conf,
1359 address: internal_create_response.address.clone(),
1360 bip21: internal_create_response.bip21.clone(),
1361 claim_public_key: crate::utils::json_to_pubkey(
1362 &internal_create_response.claim_public_key,
1363 )?,
1364 expected_amount: internal_create_response.expected_amount,
1365 referral_id: internal_create_response.referral_id,
1366 swap_tree: internal_create_response.swap_tree.clone().into(),
1367 timeout_block_height: internal_create_response.timeout_block_height,
1368 blinding_key: internal_create_response.blinding_key.clone(),
1369 };
1370 Ok(res)
1371 }
1372
1373 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1374 LBtcSwapScript::submarine_from_swap_resp(
1375 &self.get_boltz_create_response()?,
1376 self.get_refund_keypair()?.public_key().into(),
1377 )
1378 .map_err(|e| {
1379 SdkError::generic(format!(
1380 "Failed to create swap script for Send Swap {}: {e:?}",
1381 self.id
1382 ))
1383 })
1384 }
1385
1386 pub(crate) fn from_boltz_struct_to_json(
1387 create_response: &CreateSubmarineResponse,
1388 expected_swap_id: &str,
1389 ) -> Result<String, PaymentError> {
1390 let internal_create_response =
1391 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1392 create_response,
1393 expected_swap_id,
1394 )?;
1395
1396 let create_response_json =
1397 serde_json::to_string(&internal_create_response).map_err(|e| {
1398 PaymentError::Generic {
1399 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1400 }
1401 })?;
1402
1403 Ok(create_response_json)
1404 }
1405}
1406
1407#[derive(Clone, Debug, Derivative)]
1409#[derivative(PartialEq)]
1410pub struct ReceiveSwap {
1411 pub(crate) id: String,
1412 pub(crate) preimage: String,
1413 pub(crate) create_response_json: String,
1415 pub(crate) claim_private_key: String,
1416 pub(crate) invoice: String,
1417 pub(crate) bolt12_offer: Option<String>,
1419 pub(crate) payment_hash: Option<String>,
1420 pub(crate) destination_pubkey: Option<String>,
1421 pub(crate) description: Option<String>,
1422 pub(crate) payer_note: Option<String>,
1423 pub(crate) payer_amount_sat: u64,
1425 pub(crate) receiver_amount_sat: u64,
1426 pub(crate) pair_fees_json: String,
1428 pub(crate) claim_fees_sat: u64,
1429 pub(crate) claim_address: Option<String>,
1431 pub(crate) claim_tx_id: Option<String>,
1433 pub(crate) lockup_tx_id: Option<String>,
1435 pub(crate) mrh_address: String,
1437 pub(crate) mrh_tx_id: Option<String>,
1439 pub(crate) created_at: u32,
1442 pub(crate) timeout_block_height: u32,
1443 pub(crate) state: PaymentState,
1444 #[derivative(PartialEq = "ignore")]
1446 pub(crate) metadata: SwapMetadata,
1447}
1448impl ReceiveSwap {
1449 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1450 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1451 }
1452
1453 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1454 Ok(self
1455 .get_swap_script()?
1456 .funding_addrs
1457 .ok_or(anyhow!("No funding address found"))?
1458 .script_pubkey())
1459 }
1460
1461 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1462 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1463 serde_json::from_str(&self.create_response_json).map_err(|e| {
1464 PaymentError::Generic {
1465 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1466 }
1467 })?;
1468
1469 let res = CreateReverseResponse {
1470 id: self.id.clone(),
1471 invoice: Some(self.invoice.clone()),
1472 swap_tree: internal_create_response.swap_tree.clone().into(),
1473 lockup_address: internal_create_response.lockup_address.clone(),
1474 refund_public_key: crate::utils::json_to_pubkey(
1475 &internal_create_response.refund_public_key,
1476 )?,
1477 timeout_block_height: internal_create_response.timeout_block_height,
1478 onchain_amount: internal_create_response.onchain_amount,
1479 blinding_key: internal_create_response.blinding_key.clone(),
1480 };
1481 Ok(res)
1482 }
1483
1484 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1485 let keypair = self.get_claim_keypair()?;
1486 let create_response =
1487 self.get_boltz_create_response()
1488 .map_err(|e| PaymentError::Generic {
1489 err: format!(
1490 "Failed to create swap script for Receive Swap {}: {e:?}",
1491 self.id
1492 ),
1493 })?;
1494 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1495 .map_err(|e| PaymentError::Generic {
1496 err: format!(
1497 "Failed to create swap script for Receive Swap {}: {e:?}",
1498 self.id
1499 ),
1500 })
1501 }
1502
1503 pub(crate) fn from_boltz_struct_to_json(
1504 create_response: &CreateReverseResponse,
1505 expected_swap_id: &str,
1506 expected_invoice: Option<&str>,
1507 ) -> Result<String, PaymentError> {
1508 let internal_create_response =
1509 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1510 create_response,
1511 expected_swap_id,
1512 expected_invoice,
1513 )?;
1514
1515 let create_response_json =
1516 serde_json::to_string(&internal_create_response).map_err(|e| {
1517 PaymentError::Generic {
1518 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1519 }
1520 })?;
1521
1522 Ok(create_response_json)
1523 }
1524}
1525
1526#[derive(Clone, Debug, PartialEq, Serialize)]
1528pub struct RefundableSwap {
1529 pub swap_address: String,
1530 pub timestamp: u32,
1531 pub amount_sat: u64,
1533 pub last_refund_tx_id: Option<String>,
1535}
1536
1537#[derive(Clone, Debug, Derivative)]
1539#[derivative(PartialEq)]
1540pub(crate) struct Bolt12Offer {
1541 pub(crate) id: String,
1543 pub(crate) description: String,
1545 pub(crate) private_key: String,
1547 pub(crate) webhook_url: Option<String>,
1549 pub(crate) created_at: u32,
1551}
1552impl Bolt12Offer {
1553 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1554 utils::decode_keypair(&self.private_key)
1555 }
1556}
1557impl TryFrom<Bolt12Offer> for Offer {
1558 type Error = SdkError;
1559
1560 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1561 Offer::from_str(&val.id)
1562 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1563 }
1564}
1565
1566#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1568#[strum(serialize_all = "lowercase")]
1569pub enum PaymentState {
1570 #[default]
1571 Created = 0,
1572
1573 Pending = 1,
1593
1594 Complete = 2,
1606
1607 Failed = 3,
1615
1616 TimedOut = 4,
1621
1622 Refundable = 5,
1627
1628 RefundPending = 6,
1634
1635 WaitingFeeAcceptance = 7,
1647}
1648
1649impl ToSql for PaymentState {
1650 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1651 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1652 }
1653}
1654impl FromSql for PaymentState {
1655 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1656 match value {
1657 ValueRef::Integer(i) => match i as u8 {
1658 0 => Ok(PaymentState::Created),
1659 1 => Ok(PaymentState::Pending),
1660 2 => Ok(PaymentState::Complete),
1661 3 => Ok(PaymentState::Failed),
1662 4 => Ok(PaymentState::TimedOut),
1663 5 => Ok(PaymentState::Refundable),
1664 6 => Ok(PaymentState::RefundPending),
1665 7 => Ok(PaymentState::WaitingFeeAcceptance),
1666 _ => Err(FromSqlError::OutOfRange(i)),
1667 },
1668 _ => Err(FromSqlError::InvalidType),
1669 }
1670 }
1671}
1672
1673impl PaymentState {
1674 pub(crate) fn is_refundable(&self) -> bool {
1675 matches!(
1676 self,
1677 PaymentState::Refundable
1678 | PaymentState::RefundPending
1679 | PaymentState::WaitingFeeAcceptance
1680 )
1681 }
1682}
1683
1684#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1685#[strum(serialize_all = "lowercase")]
1686pub enum PaymentType {
1687 Receive = 0,
1688 Send = 1,
1689}
1690impl From<Direction> for PaymentType {
1691 fn from(value: Direction) -> Self {
1692 match value {
1693 Direction::Incoming => Self::Receive,
1694 Direction::Outgoing => Self::Send,
1695 }
1696 }
1697}
1698impl ToSql for PaymentType {
1699 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1700 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1701 }
1702}
1703impl FromSql for PaymentType {
1704 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1705 match value {
1706 ValueRef::Integer(i) => match i as u8 {
1707 0 => Ok(PaymentType::Receive),
1708 1 => Ok(PaymentType::Send),
1709 _ => Err(FromSqlError::OutOfRange(i)),
1710 },
1711 _ => Err(FromSqlError::InvalidType),
1712 }
1713 }
1714}
1715
1716#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1717pub enum PaymentStatus {
1718 Pending = 0,
1719 Complete = 1,
1720}
1721impl ToSql for PaymentStatus {
1722 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1723 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1724 }
1725}
1726impl FromSql for PaymentStatus {
1727 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1728 match value {
1729 ValueRef::Integer(i) => match i as u8 {
1730 0 => Ok(PaymentStatus::Pending),
1731 1 => Ok(PaymentStatus::Complete),
1732 _ => Err(FromSqlError::OutOfRange(i)),
1733 },
1734 _ => Err(FromSqlError::InvalidType),
1735 }
1736 }
1737}
1738
1739#[derive(Debug, Clone, Serialize)]
1740pub struct PaymentTxData {
1741 pub tx_id: String,
1743
1744 pub timestamp: Option<u32>,
1746
1747 pub asset_id: String,
1749
1750 pub amount: u64,
1754
1755 pub fees_sat: u64,
1757
1758 pub payment_type: PaymentType,
1759
1760 pub is_confirmed: bool,
1762
1763 pub unblinding_data: Option<String>,
1766}
1767
1768#[derive(Debug, Clone, Serialize)]
1769pub enum PaymentSwapType {
1770 Receive,
1771 Send,
1772 Chain,
1773}
1774
1775#[derive(Debug, Clone, Serialize)]
1776pub struct PaymentSwapData {
1777 pub swap_id: String,
1778
1779 pub swap_type: PaymentSwapType,
1780
1781 pub created_at: u32,
1783
1784 pub expiration_blockheight: u32,
1786
1787 pub preimage: Option<String>,
1788 pub invoice: Option<String>,
1789 pub bolt12_offer: Option<String>,
1790 pub payment_hash: Option<String>,
1791 pub destination_pubkey: Option<String>,
1792 pub description: String,
1793 pub payer_note: Option<String>,
1794
1795 pub payer_amount_sat: u64,
1797
1798 pub receiver_amount_sat: u64,
1800
1801 pub swapper_fees_sat: u64,
1803
1804 pub refund_tx_id: Option<String>,
1805 pub refund_tx_amount_sat: Option<u64>,
1806
1807 pub bitcoin_address: Option<String>,
1810
1811 pub status: PaymentState,
1813}
1814
1815#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1817pub struct LnUrlInfo {
1818 pub ln_address: Option<String>,
1819 pub lnurl_pay_comment: Option<String>,
1820 pub lnurl_pay_domain: Option<String>,
1821 pub lnurl_pay_metadata: Option<String>,
1822 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1823 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1824 pub lnurl_withdraw_endpoint: Option<String>,
1825}
1826
1827#[derive(Debug, Clone, Serialize)]
1831pub struct AssetMetadata {
1832 pub asset_id: String,
1834 pub name: String,
1836 pub ticker: String,
1838 pub precision: u8,
1841 pub fiat_id: Option<String>,
1843}
1844
1845impl AssetMetadata {
1846 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1847 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1848 }
1849
1850 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1851 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1852 }
1853}
1854
1855#[derive(Clone, Debug, PartialEq, Serialize)]
1858pub struct AssetInfo {
1859 pub name: String,
1861 pub ticker: String,
1863 pub amount: f64,
1866 pub fees: Option<f64>,
1869}
1870
1871#[derive(Debug, Clone, PartialEq, Serialize)]
1873#[allow(clippy::large_enum_variant)]
1874pub enum PaymentDetails {
1875 Lightning {
1877 swap_id: String,
1878
1879 description: String,
1881
1882 liquid_expiration_blockheight: u32,
1884
1885 preimage: Option<String>,
1887
1888 invoice: Option<String>,
1892
1893 bolt12_offer: Option<String>,
1894
1895 payment_hash: Option<String>,
1897
1898 destination_pubkey: Option<String>,
1900
1901 lnurl_info: Option<LnUrlInfo>,
1903
1904 bip353_address: Option<String>,
1906
1907 payer_note: Option<String>,
1909
1910 claim_tx_id: Option<String>,
1912
1913 refund_tx_id: Option<String>,
1915
1916 refund_tx_amount_sat: Option<u64>,
1918 },
1919 Liquid {
1921 destination: String,
1923
1924 description: String,
1926
1927 asset_id: String,
1929
1930 asset_info: Option<AssetInfo>,
1932
1933 lnurl_info: Option<LnUrlInfo>,
1935
1936 bip353_address: Option<String>,
1938
1939 payer_note: Option<String>,
1941 },
1942 Bitcoin {
1944 swap_id: String,
1945
1946 bitcoin_address: String,
1948
1949 description: String,
1951
1952 auto_accepted_fees: bool,
1956
1957 liquid_expiration_blockheight: Option<u32>,
1960
1961 bitcoin_expiration_blockheight: Option<u32>,
1964
1965 lockup_tx_id: Option<String>,
1967
1968 claim_tx_id: Option<String>,
1970
1971 refund_tx_id: Option<String>,
1973
1974 refund_tx_amount_sat: Option<u64>,
1976 },
1977}
1978
1979impl PaymentDetails {
1980 pub(crate) fn get_swap_id(&self) -> Option<String> {
1981 match self {
1982 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
1983 Some(swap_id.clone())
1984 }
1985 Self::Liquid { .. } => None,
1986 }
1987 }
1988
1989 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
1990 match self {
1991 Self::Lightning {
1992 refund_tx_amount_sat,
1993 ..
1994 }
1995 | Self::Bitcoin {
1996 refund_tx_amount_sat,
1997 ..
1998 } => *refund_tx_amount_sat,
1999 Self::Liquid { .. } => None,
2000 }
2001 }
2002
2003 pub(crate) fn get_description(&self) -> Option<String> {
2004 match self {
2005 Self::Lightning { description, .. }
2006 | Self::Bitcoin { description, .. }
2007 | Self::Liquid { description, .. } => Some(description.clone()),
2008 }
2009 }
2010
2011 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2012 match self {
2013 Self::Liquid { asset_id, .. } => {
2014 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2015 }
2016 _ => true,
2017 }
2018 }
2019}
2020
2021#[derive(Debug, Clone, PartialEq, Serialize)]
2025pub struct Payment {
2026 pub destination: Option<String>,
2029
2030 pub tx_id: Option<String>,
2031
2032 pub unblinding_data: Option<String>,
2035
2036 pub timestamp: u32,
2042
2043 pub amount_sat: u64,
2047
2048 pub fees_sat: u64,
2062
2063 pub swapper_fees_sat: Option<u64>,
2066
2067 pub payment_type: PaymentType,
2069
2070 pub status: PaymentState,
2076
2077 pub details: PaymentDetails,
2080}
2081impl Payment {
2082 pub(crate) fn from_pending_swap(
2083 swap: PaymentSwapData,
2084 payment_type: PaymentType,
2085 payment_details: PaymentDetails,
2086 ) -> Payment {
2087 let amount_sat = match payment_type {
2088 PaymentType::Receive => swap.receiver_amount_sat,
2089 PaymentType::Send => swap.payer_amount_sat,
2090 };
2091
2092 Payment {
2093 destination: swap.invoice.clone(),
2094 tx_id: None,
2095 unblinding_data: None,
2096 timestamp: swap.created_at,
2097 amount_sat,
2098 fees_sat: swap
2099 .payer_amount_sat
2100 .saturating_sub(swap.receiver_amount_sat),
2101 swapper_fees_sat: Some(swap.swapper_fees_sat),
2102 payment_type,
2103 status: swap.status,
2104 details: payment_details,
2105 }
2106 }
2107
2108 pub(crate) fn from_tx_data(
2109 tx: PaymentTxData,
2110 swap: Option<PaymentSwapData>,
2111 details: PaymentDetails,
2112 ) -> Payment {
2113 let (amount_sat, fees_sat) = match swap.as_ref() {
2114 Some(s) => match tx.payment_type {
2115 PaymentType::Receive => (tx.amount, s.payer_amount_sat.saturating_sub(tx.amount)),
2119 PaymentType::Send => (
2120 s.receiver_amount_sat,
2121 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2122 ),
2123 },
2124 None => {
2125 let (amount_sat, fees_sat) = match tx.payment_type {
2126 PaymentType::Receive => (tx.amount, 0),
2127 PaymentType::Send => (tx.amount, tx.fees_sat),
2128 };
2129 match details {
2132 PaymentDetails::Liquid {
2133 asset_info: Some(ref asset_info),
2134 ..
2135 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2136 _ => (amount_sat, fees_sat),
2137 }
2138 }
2139 };
2140 Payment {
2141 tx_id: Some(tx.tx_id),
2142 unblinding_data: tx.unblinding_data,
2143 destination: match &swap {
2147 Some(PaymentSwapData {
2148 swap_type: PaymentSwapType::Receive,
2149 invoice,
2150 ..
2151 }) => invoice.clone(),
2152 Some(PaymentSwapData {
2153 swap_type: PaymentSwapType::Send,
2154 invoice,
2155 bolt12_offer,
2156 ..
2157 }) => bolt12_offer.clone().or(invoice.clone()),
2158 Some(PaymentSwapData {
2159 swap_type: PaymentSwapType::Chain,
2160 bitcoin_address,
2161 ..
2162 }) => bitcoin_address.clone(),
2163 _ => match &details {
2164 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2165 _ => None,
2166 },
2167 },
2168 timestamp: tx
2169 .timestamp
2170 .or(swap.as_ref().map(|s| s.created_at))
2171 .unwrap_or(utils::now()),
2172 amount_sat,
2173 fees_sat,
2174 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2175 payment_type: tx.payment_type,
2176 status: match &swap {
2177 Some(swap) => swap.status,
2178 None => match tx.is_confirmed {
2179 true => PaymentState::Complete,
2180 false => PaymentState::Pending,
2181 },
2182 },
2183 details,
2184 }
2185 }
2186
2187 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2188 match self.details.clone() {
2189 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2190 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2191 PaymentDetails::Liquid { .. } => None,
2192 }
2193 .flatten()
2194 }
2195}
2196
2197#[derive(Deserialize, Serialize, Clone, Debug)]
2199#[serde(rename_all = "camelCase")]
2200pub struct RecommendedFees {
2201 pub fastest_fee: u64,
2202 pub half_hour_fee: u64,
2203 pub hour_fee: u64,
2204 pub economy_fee: u64,
2205 pub minimum_fee: u64,
2206}
2207
2208#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2210pub enum BuyBitcoinProvider {
2211 #[strum(serialize = "moonpay")]
2212 Moonpay,
2213}
2214
2215#[derive(Debug, Serialize)]
2217pub struct PrepareBuyBitcoinRequest {
2218 pub provider: BuyBitcoinProvider,
2219 pub amount_sat: u64,
2220}
2221
2222#[derive(Clone, Debug, Serialize)]
2224pub struct PrepareBuyBitcoinResponse {
2225 pub provider: BuyBitcoinProvider,
2226 pub amount_sat: u64,
2227 pub fees_sat: u64,
2228}
2229
2230#[derive(Clone, Debug, Serialize)]
2232pub struct BuyBitcoinRequest {
2233 pub prepare_response: PrepareBuyBitcoinResponse,
2234 pub redirect_url: Option<String>,
2238}
2239
2240#[derive(Clone, Debug)]
2242pub struct LogEntry {
2243 pub line: String,
2244 pub level: String,
2245}
2246
2247#[derive(Clone, Debug, Serialize, Deserialize)]
2248struct InternalLeaf {
2249 pub output: String,
2250 pub version: u8,
2251}
2252impl From<InternalLeaf> for Leaf {
2253 fn from(value: InternalLeaf) -> Self {
2254 Leaf {
2255 output: value.output,
2256 version: value.version,
2257 }
2258 }
2259}
2260impl From<Leaf> for InternalLeaf {
2261 fn from(value: Leaf) -> Self {
2262 InternalLeaf {
2263 output: value.output,
2264 version: value.version,
2265 }
2266 }
2267}
2268
2269#[derive(Clone, Debug, Serialize, Deserialize)]
2270pub(super) struct InternalSwapTree {
2271 claim_leaf: InternalLeaf,
2272 refund_leaf: InternalLeaf,
2273}
2274impl From<InternalSwapTree> for SwapTree {
2275 fn from(value: InternalSwapTree) -> Self {
2276 SwapTree {
2277 claim_leaf: value.claim_leaf.into(),
2278 refund_leaf: value.refund_leaf.into(),
2279 }
2280 }
2281}
2282impl From<SwapTree> for InternalSwapTree {
2283 fn from(value: SwapTree) -> Self {
2284 InternalSwapTree {
2285 claim_leaf: value.claim_leaf.into(),
2286 refund_leaf: value.refund_leaf.into(),
2287 }
2288 }
2289}
2290
2291#[derive(Debug, Serialize)]
2293pub struct PrepareLnUrlPayRequest {
2294 pub data: LnUrlPayRequestData,
2296 pub amount: PayAmount,
2298 pub bip353_address: Option<String>,
2301 pub comment: Option<String>,
2304 pub validate_success_action_url: Option<bool>,
2307}
2308
2309#[derive(Debug, Serialize)]
2311pub struct PrepareLnUrlPayResponse {
2312 pub destination: SendDestination,
2314 pub fees_sat: u64,
2316 pub data: LnUrlPayRequestData,
2318 pub amount: PayAmount,
2320 pub comment: Option<String>,
2323 pub success_action: Option<SuccessAction>,
2326}
2327
2328#[derive(Debug, Serialize)]
2330pub struct LnUrlPayRequest {
2331 pub prepare_response: PrepareLnUrlPayResponse,
2333}
2334
2335#[derive(Serialize)]
2347#[allow(clippy::large_enum_variant)]
2348pub enum LnUrlPayResult {
2349 EndpointSuccess { data: LnUrlPaySuccessData },
2350 EndpointError { data: LnUrlErrorData },
2351 PayError { data: LnUrlPayErrorData },
2352}
2353
2354#[derive(Serialize)]
2355pub struct LnUrlPaySuccessData {
2356 pub payment: Payment,
2357 pub success_action: Option<SuccessActionProcessed>,
2358}
2359
2360#[derive(Debug, Clone)]
2361pub enum Transaction {
2362 Liquid(boltz_client::elements::Transaction),
2363 Bitcoin(boltz_client::bitcoin::Transaction),
2364}
2365
2366impl Transaction {
2367 pub(crate) fn txid(&self) -> String {
2368 match self {
2369 Transaction::Liquid(tx) => tx.txid().to_hex(),
2370 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2371 }
2372 }
2373}
2374
2375#[derive(Debug, Clone)]
2376pub enum Utxo {
2377 Liquid(
2378 Box<(
2379 boltz_client::elements::OutPoint,
2380 boltz_client::elements::TxOut,
2381 )>,
2382 ),
2383 Bitcoin(
2384 (
2385 boltz_client::bitcoin::OutPoint,
2386 boltz_client::bitcoin::TxOut,
2387 ),
2388 ),
2389}
2390
2391impl Utxo {
2392 pub(crate) fn as_bitcoin(
2393 &self,
2394 ) -> Option<&(
2395 boltz_client::bitcoin::OutPoint,
2396 boltz_client::bitcoin::TxOut,
2397 )> {
2398 match self {
2399 Utxo::Liquid(_) => None,
2400 Utxo::Bitcoin(utxo) => Some(utxo),
2401 }
2402 }
2403
2404 pub(crate) fn as_liquid(
2405 &self,
2406 ) -> Option<
2407 Box<(
2408 boltz_client::elements::OutPoint,
2409 boltz_client::elements::TxOut,
2410 )>,
2411 > {
2412 match self {
2413 Utxo::Bitcoin(_) => None,
2414 Utxo::Liquid(utxo) => Some(utxo.clone()),
2415 }
2416 }
2417}
2418
2419#[derive(Debug, Clone)]
2421pub struct FetchPaymentProposedFeesRequest {
2422 pub swap_id: String,
2423}
2424
2425#[derive(Debug, Clone, Serialize)]
2427pub struct FetchPaymentProposedFeesResponse {
2428 pub swap_id: String,
2429 pub fees_sat: u64,
2430 pub payer_amount_sat: u64,
2432 pub receiver_amount_sat: u64,
2434}
2435
2436#[derive(Debug, Clone)]
2438pub struct AcceptPaymentProposedFeesRequest {
2439 pub response: FetchPaymentProposedFeesResponse,
2440}
2441
2442#[derive(Clone, Debug)]
2443pub struct History<T> {
2444 pub txid: T,
2445 pub height: i32,
2450}
2451pub(crate) type LBtcHistory = History<elements::Txid>;
2452pub(crate) type BtcHistory = History<bitcoin::Txid>;
2453
2454impl<T> History<T> {
2455 pub(crate) fn confirmed(&self) -> bool {
2456 self.height > 0
2457 }
2458}
2459#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2460impl From<electrum_client::GetHistoryRes> for BtcHistory {
2461 fn from(value: electrum_client::GetHistoryRes) -> Self {
2462 Self {
2463 txid: value.tx_hash,
2464 height: value.height,
2465 }
2466 }
2467}
2468impl From<lwk_wollet::History> for LBtcHistory {
2469 fn from(value: lwk_wollet::History) -> Self {
2470 Self::from(&value)
2471 }
2472}
2473impl From<&lwk_wollet::History> for LBtcHistory {
2474 fn from(value: &lwk_wollet::History) -> Self {
2475 Self {
2476 txid: value.txid,
2477 height: value.height,
2478 }
2479 }
2480}
2481pub(crate) type BtcScript = bitcoin::ScriptBuf;
2482pub(crate) type LBtcScript = elements::Script;
2483
2484#[derive(Clone, Debug)]
2485pub struct BtcScriptBalance {
2486 pub confirmed: u64,
2488 pub unconfirmed: i64,
2492}
2493#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2494impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2495 fn from(val: electrum_client::GetBalanceRes) -> Self {
2496 Self {
2497 confirmed: val.confirmed,
2498 unconfirmed: val.unconfirmed,
2499 }
2500 }
2501}
2502
2503#[macro_export]
2504macro_rules! get_updated_fields {
2505 ($($var:ident),* $(,)?) => {{
2506 let mut options = Vec::new();
2507 $(
2508 if $var.is_some() {
2509 options.push(stringify!($var).to_string());
2510 }
2511 )*
2512 match options.len() > 0 {
2513 true => Some(options),
2514 false => None,
2515 }
2516 }};
2517}