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 network: LiquidNetwork,
68 pub payment_timeout_sec: u64,
70 pub sync_service_url: Option<String>,
73 pub zero_conf_max_amount_sat: Option<u64>,
76 pub breez_api_key: Option<String>,
78 pub external_input_parsers: Option<Vec<ExternalInputParser>>,
82 pub use_default_external_input_parsers: bool,
86 pub onchain_fee_rate_leeway_sat_per_vbyte: Option<u32>,
93 pub asset_metadata: Option<Vec<AssetMetadata>>,
98 pub sideswap_api_key: Option<String>,
100}
101
102impl Config {
103 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
104 pub fn mainnet(breez_api_key: Option<String>) -> Self {
105 Config {
106 liquid_explorer: BlockchainExplorer::Electrum {
107 url: "elements-mainnet.breez.technology:50002".to_string(),
108 },
109 bitcoin_explorer: BlockchainExplorer::Electrum {
110 url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
111 },
112 working_dir: ".".to_string(),
113 network: LiquidNetwork::Mainnet,
114 payment_timeout_sec: 15,
115 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
116 zero_conf_max_amount_sat: None,
117 breez_api_key,
118 external_input_parsers: None,
119 use_default_external_input_parsers: true,
120 onchain_fee_rate_leeway_sat_per_vbyte: None,
121 asset_metadata: None,
122 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
123 }
124 }
125
126 pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
127 Config {
128 liquid_explorer: BlockchainExplorer::Esplora {
129 url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
130 use_waterfalls: true,
131 },
132 bitcoin_explorer: BlockchainExplorer::Esplora {
133 url: "https://blockstream.info/api/".to_string(),
134 use_waterfalls: false,
135 },
136 working_dir: ".".to_string(),
137 network: LiquidNetwork::Mainnet,
138 payment_timeout_sec: 15,
139 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
140 zero_conf_max_amount_sat: None,
141 breez_api_key,
142 external_input_parsers: None,
143 use_default_external_input_parsers: true,
144 onchain_fee_rate_leeway_sat_per_vbyte: None,
145 asset_metadata: None,
146 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
147 }
148 }
149
150 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
151 pub fn testnet(breez_api_key: Option<String>) -> Self {
152 Config {
153 liquid_explorer: BlockchainExplorer::Electrum {
154 url: "elements-testnet.blockstream.info:50002".to_string(),
155 },
156 bitcoin_explorer: BlockchainExplorer::Electrum {
157 url: "bitcoin-testnet.blockstream.info:50002".to_string(),
158 },
159 working_dir: ".".to_string(),
160 network: LiquidNetwork::Testnet,
161 payment_timeout_sec: 15,
162 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
163 zero_conf_max_amount_sat: None,
164 breez_api_key,
165 external_input_parsers: None,
166 use_default_external_input_parsers: true,
167 onchain_fee_rate_leeway_sat_per_vbyte: None,
168 asset_metadata: None,
169 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
170 }
171 }
172
173 pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
174 Config {
175 liquid_explorer: BlockchainExplorer::Esplora {
176 url: "https://blockstream.info/liquidtestnet/api".to_string(),
177 use_waterfalls: false,
178 },
179 bitcoin_explorer: BlockchainExplorer::Esplora {
180 url: "https://blockstream.info/testnet/api/".to_string(),
181 use_waterfalls: false,
182 },
183 working_dir: ".".to_string(),
184 network: LiquidNetwork::Testnet,
185 payment_timeout_sec: 15,
186 sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
187 zero_conf_max_amount_sat: None,
188 breez_api_key,
189 external_input_parsers: None,
190 use_default_external_input_parsers: true,
191 onchain_fee_rate_leeway_sat_per_vbyte: None,
192 asset_metadata: None,
193 sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
194 }
195 }
196
197 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
198 pub fn regtest() -> Self {
199 Config {
200 liquid_explorer: BlockchainExplorer::Electrum {
201 url: "localhost:19002".to_string(),
202 },
203 bitcoin_explorer: BlockchainExplorer::Electrum {
204 url: "localhost:19001".to_string(),
205 },
206 working_dir: ".".to_string(),
207 network: LiquidNetwork::Regtest,
208 payment_timeout_sec: 15,
209 sync_service_url: Some("http://localhost:8088".to_string()),
210 zero_conf_max_amount_sat: None,
211 breez_api_key: None,
212 external_input_parsers: None,
213 use_default_external_input_parsers: true,
214 onchain_fee_rate_leeway_sat_per_vbyte: None,
215 asset_metadata: None,
216 sideswap_api_key: None,
217 }
218 }
219
220 pub fn regtest_esplora() -> Self {
221 Config {
222 liquid_explorer: BlockchainExplorer::Esplora {
223 url: "http://localhost:3120/api".to_string(),
224 use_waterfalls: true,
225 },
226 bitcoin_explorer: BlockchainExplorer::Esplora {
227 url: "http://localhost:4002/api".to_string(),
228 use_waterfalls: false,
229 },
230 working_dir: ".".to_string(),
231 network: LiquidNetwork::Regtest,
232 payment_timeout_sec: 15,
233 sync_service_url: Some("http://localhost:8089".to_string()),
234 zero_conf_max_amount_sat: None,
235 breez_api_key: None,
236 external_input_parsers: None,
237 use_default_external_input_parsers: true,
238 onchain_fee_rate_leeway_sat_per_vbyte: None,
239 asset_metadata: None,
240 sideswap_api_key: None,
241 }
242 }
243
244 pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
245 Ok(PathBuf::from(base_dir)
246 .join(match self.network {
247 LiquidNetwork::Mainnet => "mainnet",
248 LiquidNetwork::Testnet => "testnet",
249 LiquidNetwork::Regtest => "regtest",
250 })
251 .join(fingerprint_hex)
252 .to_str()
253 .ok_or(anyhow::anyhow!(
254 "Could not get retrieve current wallet directory"
255 ))?
256 .to_string())
257 }
258
259 pub fn zero_conf_max_amount_sat(&self) -> u64 {
260 self.zero_conf_max_amount_sat
261 .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
262 }
263
264 pub(crate) fn lbtc_asset_id(&self) -> String {
265 utils::lbtc_asset_id(self.network).to_string()
266 }
267
268 pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
269 let mut external_input_parsers = Vec::new();
270 if self.use_default_external_input_parsers {
271 let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
272 .iter()
273 .map(|(id, regex, url)| ExternalInputParser {
274 provider_id: id.to_string(),
275 input_regex: regex.to_string(),
276 parser_url: url.to_string(),
277 })
278 .collect::<Vec<_>>();
279 external_input_parsers.extend(default_parsers);
280 }
281 external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
282
283 external_input_parsers
284 }
285
286 pub(crate) fn default_boltz_url(&self) -> &str {
287 match self.network {
288 LiquidNetwork::Mainnet => BOLTZ_MAINNET_URL_V2,
289 LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
290 LiquidNetwork::Regtest => BOLTZ_REGTEST,
291 }
292 }
293
294 pub fn sync_enabled(&self) -> bool {
295 self.sync_service_url.is_some()
296 }
297
298 pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
299 match self.bitcoin_explorer {
300 BlockchainExplorer::Esplora { .. } => {
301 Arc::new(EsploraBitcoinChainService::new(self.clone()))
302 }
303 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
304 BlockchainExplorer::Electrum { .. } => Arc::new(
305 crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
306 ),
307 }
308 }
309
310 pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
311 match &self.liquid_explorer {
312 BlockchainExplorer::Esplora { url, .. } => {
313 if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
314 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")
315 }
316 Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
317 }
318 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
319 BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
320 crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
321 )),
322 }
323 }
324
325 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
326 pub(crate) fn electrum_tls_options(&self) -> (bool, bool) {
327 match self.network {
328 LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
329 LiquidNetwork::Regtest => (false, false),
330 }
331 }
332
333 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
334 pub(crate) fn electrum_client(
335 &self,
336 url: &str,
337 ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
338 let (tls, validate_domain) = self.electrum_tls_options();
339 let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
340 lwk_wollet::ElectrumClient::with_options(
341 &electrum_url,
342 lwk_wollet::ElectrumOptions { timeout: Some(3) },
343 )
344 }
345}
346
347#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
350pub enum LiquidNetwork {
351 Mainnet,
353 Testnet,
355 Regtest,
357}
358impl LiquidNetwork {
359 pub fn as_bitcoin_chain(&self) -> BitcoinChain {
360 match self {
361 LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
362 LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
363 LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
364 }
365 }
366}
367
368impl From<LiquidNetwork> for ElementsNetwork {
369 fn from(value: LiquidNetwork) -> Self {
370 match value {
371 LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
372 LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
373 LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
374 policy_asset: AssetId::from_str(
375 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
376 )
377 .unwrap(),
378 },
379 }
380 }
381}
382
383impl From<LiquidNetwork> for Chain {
384 fn from(value: LiquidNetwork) -> Self {
385 Chain::Liquid(value.into())
386 }
387}
388
389impl From<LiquidNetwork> for LiquidChain {
390 fn from(value: LiquidNetwork) -> Self {
391 match value {
392 LiquidNetwork::Mainnet => LiquidChain::Liquid,
393 LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
394 LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
395 }
396 }
397}
398
399impl TryFrom<&str> for LiquidNetwork {
400 type Error = anyhow::Error;
401
402 fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
403 match value.to_lowercase().as_str() {
404 "mainnet" => Ok(LiquidNetwork::Mainnet),
405 "testnet" => Ok(LiquidNetwork::Testnet),
406 "regtest" => Ok(LiquidNetwork::Regtest),
407 _ => Err(anyhow!("Invalid network")),
408 }
409 }
410}
411
412impl From<LiquidNetwork> for Network {
413 fn from(value: LiquidNetwork) -> Self {
414 match value {
415 LiquidNetwork::Mainnet => Self::Bitcoin,
416 LiquidNetwork::Testnet => Self::Testnet,
417 LiquidNetwork::Regtest => Self::Regtest,
418 }
419 }
420}
421
422impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
423 fn from(value: LiquidNetwork) -> Self {
424 match value {
425 LiquidNetwork::Mainnet => Self::Bitcoin,
426 LiquidNetwork::Testnet => Self::Testnet,
427 LiquidNetwork::Regtest => Self::Regtest,
428 }
429 }
430}
431
432impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
433 fn from(value: LiquidNetwork) -> Self {
434 match value {
435 LiquidNetwork::Mainnet => Self::Bitcoin,
436 LiquidNetwork::Testnet => Self::Testnet,
437 LiquidNetwork::Regtest => Self::Regtest,
438 }
439 }
440}
441
442pub trait EventListener: MaybeSend + MaybeSync {
444 fn on_event(&self, e: SdkEvent);
445}
446
447#[derive(Clone, Debug, PartialEq)]
450pub enum SdkEvent {
451 PaymentFailed {
452 details: Payment,
453 },
454 PaymentPending {
455 details: Payment,
456 },
457 PaymentRefundable {
458 details: Payment,
459 },
460 PaymentRefunded {
461 details: Payment,
462 },
463 PaymentRefundPending {
464 details: Payment,
465 },
466 PaymentSucceeded {
467 details: Payment,
468 },
469 PaymentWaitingConfirmation {
470 details: Payment,
471 },
472 PaymentWaitingFeeAcceptance {
473 details: Payment,
474 },
475 Synced,
477 DataSynced {
479 did_pull_new_records: bool,
481 },
482}
483
484#[derive(thiserror::Error, Debug)]
485pub enum SignerError {
486 #[error("Signer error: {err}")]
487 Generic { err: String },
488}
489
490impl From<anyhow::Error> for SignerError {
491 fn from(err: anyhow::Error) -> Self {
492 SignerError::Generic {
493 err: err.to_string(),
494 }
495 }
496}
497
498impl From<bip32::Error> for SignerError {
499 fn from(err: bip32::Error) -> Self {
500 SignerError::Generic {
501 err: err.to_string(),
502 }
503 }
504}
505
506pub trait Signer: MaybeSend + MaybeSync {
509 fn xpub(&self) -> Result<Vec<u8>, SignerError>;
512
513 fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
519
520 fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
522
523 fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
525
526 fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
528
529 fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
532
533 fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
535
536 fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
538}
539
540pub struct ConnectRequest {
543 pub config: Config,
545 pub mnemonic: Option<String>,
547 pub passphrase: Option<String>,
549 pub seed: Option<Vec<u8>>,
551}
552
553pub struct ConnectWithSignerRequest {
554 pub config: Config,
555}
556
557#[derive(Clone, Debug)]
560pub(crate) struct ReservedAddress {
561 pub(crate) address: String,
563 pub(crate) expiry_block_height: u32,
565}
566
567#[derive(Clone, Debug, Serialize)]
569pub enum PaymentMethod {
570 #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
571 Lightning,
572 Bolt11Invoice,
573 Bolt12Offer,
574 BitcoinAddress,
575 LiquidAddress,
576}
577
578#[derive(Debug, Serialize, Clone)]
579pub enum ReceiveAmount {
580 Bitcoin { payer_amount_sat: u64 },
582
583 Asset {
585 asset_id: String,
586 payer_amount: Option<f64>,
587 },
588}
589
590#[derive(Debug, Serialize)]
592pub struct PrepareReceiveRequest {
593 pub payment_method: PaymentMethod,
594 pub amount: Option<ReceiveAmount>,
596}
597
598#[derive(Debug, Serialize, Clone)]
600pub struct PrepareReceiveResponse {
601 pub payment_method: PaymentMethod,
602 pub fees_sat: u64,
611 pub amount: Option<ReceiveAmount>,
613 pub min_payer_amount_sat: Option<u64>,
617 pub max_payer_amount_sat: Option<u64>,
621 pub swapper_feerate: Option<f64>,
625}
626
627#[derive(Debug, Serialize)]
629pub struct ReceivePaymentRequest {
630 pub prepare_response: PrepareReceiveResponse,
631 pub description: Option<String>,
633 pub use_description_hash: Option<bool>,
635}
636
637#[derive(Debug, Serialize)]
639pub struct ReceivePaymentResponse {
640 pub destination: String,
643}
644
645#[derive(Debug, Serialize)]
647pub struct CreateBolt12InvoiceRequest {
648 pub offer: String,
650 pub invoice_request: String,
652}
653
654#[derive(Debug, Serialize, Clone)]
656pub struct CreateBolt12InvoiceResponse {
657 pub invoice: String,
659}
660
661#[derive(Debug, Serialize)]
663pub struct Limits {
664 pub min_sat: u64,
665 pub max_sat: u64,
666 pub max_zero_conf_sat: u64,
667}
668
669#[derive(Debug, Serialize)]
671pub struct LightningPaymentLimitsResponse {
672 pub send: Limits,
674 pub receive: Limits,
676}
677
678#[derive(Debug, Serialize)]
680pub struct OnchainPaymentLimitsResponse {
681 pub send: Limits,
683 pub receive: Limits,
685}
686
687#[derive(Debug, Serialize, Clone)]
689pub struct PrepareSendRequest {
690 pub destination: String,
693 pub amount: Option<PayAmount>,
696 pub comment: Option<String>,
698}
699
700#[derive(Clone, Debug, Serialize)]
702pub enum SendDestination {
703 LiquidAddress {
704 address_data: liquid::LiquidAddressData,
705 bip353_address: Option<String>,
707 },
708 Bolt11 {
709 invoice: LNInvoice,
710 bip353_address: Option<String>,
712 },
713 Bolt12 {
714 offer: LNOffer,
715 receiver_amount_sat: u64,
716 bip353_address: Option<String>,
718 payer_note: Option<String>,
720 },
721}
722
723#[derive(Debug, Serialize, Clone)]
725pub struct PrepareSendResponse {
726 pub destination: SendDestination,
727 pub amount: Option<PayAmount>,
729 pub fees_sat: Option<u64>,
732 pub estimated_asset_fees: Option<f64>,
736}
737
738#[derive(Debug, Serialize)]
740pub struct SendPaymentRequest {
741 pub prepare_response: PrepareSendResponse,
742 pub use_asset_fees: Option<bool>,
743}
744
745#[derive(Debug, Serialize)]
747pub struct SendPaymentResponse {
748 pub payment: Payment,
749}
750
751#[derive(Debug, Serialize, Clone)]
753pub enum PayAmount {
754 Bitcoin { receiver_amount_sat: u64 },
756
757 Asset {
759 asset_id: String,
760 receiver_amount: f64,
761 estimate_asset_fees: Option<bool>,
762 },
763
764 Drain,
766}
767
768#[derive(Debug, Serialize, Clone)]
770pub struct PreparePayOnchainRequest {
771 pub amount: PayAmount,
773 pub fee_rate_sat_per_vbyte: Option<u32>,
775}
776
777#[derive(Debug, Serialize, Clone)]
779pub struct PreparePayOnchainResponse {
780 pub receiver_amount_sat: u64,
781 pub claim_fees_sat: u64,
782 pub total_fees_sat: u64,
783}
784
785#[derive(Debug, Serialize)]
787pub struct PayOnchainRequest {
788 pub address: String,
789 pub prepare_response: PreparePayOnchainResponse,
790}
791
792#[derive(Debug, Serialize)]
794pub struct PrepareRefundRequest {
795 pub swap_address: String,
797 pub refund_address: String,
799 pub fee_rate_sat_per_vbyte: u32,
801}
802
803#[derive(Debug, Serialize)]
805pub struct PrepareRefundResponse {
806 pub tx_vsize: u32,
807 pub tx_fee_sat: u64,
808 pub last_refund_tx_id: Option<String>,
810}
811
812#[derive(Debug, Serialize)]
814pub struct RefundRequest {
815 pub swap_address: String,
817 pub refund_address: String,
819 pub fee_rate_sat_per_vbyte: u32,
821}
822
823#[derive(Debug, Serialize)]
825pub struct RefundResponse {
826 pub refund_tx_id: String,
827}
828
829#[derive(Clone, Debug, Default, Serialize, Deserialize)]
831pub struct AssetBalance {
832 pub asset_id: String,
833 pub balance_sat: u64,
834 pub name: Option<String>,
835 pub ticker: Option<String>,
836 pub balance: Option<f64>,
837}
838
839#[derive(Debug, Serialize, Deserialize, Default)]
840pub struct BlockchainInfo {
841 pub liquid_tip: u32,
842 pub bitcoin_tip: u32,
843}
844
845#[derive(Copy, Clone)]
846pub(crate) struct ChainTips {
847 pub liquid_tip: u32,
848 pub bitcoin_tip: u32,
849}
850
851#[derive(Debug, Serialize, Deserialize)]
852pub struct WalletInfo {
853 pub balance_sat: u64,
855 pub pending_send_sat: u64,
857 pub pending_receive_sat: u64,
859 pub fingerprint: String,
861 pub pubkey: String,
863 #[serde(default)]
865 pub asset_balances: Vec<AssetBalance>,
866}
867
868impl WalletInfo {
869 pub(crate) fn validate_sufficient_funds(
870 &self,
871 network: LiquidNetwork,
872 amount_sat: u64,
873 fees_sat: Option<u64>,
874 asset_id: &str,
875 ) -> Result<(), PaymentError> {
876 let fees_sat = fees_sat.unwrap_or(0);
877 if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
878 ensure_sdk!(
879 amount_sat + fees_sat <= self.balance_sat,
880 PaymentError::InsufficientFunds
881 );
882 } else {
883 match self
884 .asset_balances
885 .iter()
886 .find(|ab| ab.asset_id.eq(asset_id))
887 {
888 Some(asset_balance) => ensure_sdk!(
889 amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
890 PaymentError::InsufficientFunds
891 ),
892 None => return Err(PaymentError::InsufficientFunds),
893 }
894 }
895 Ok(())
896 }
897}
898
899#[derive(Debug, Serialize, Deserialize)]
901pub struct GetInfoResponse {
902 pub wallet_info: WalletInfo,
904 #[serde(default)]
906 pub blockchain_info: BlockchainInfo,
907}
908
909#[derive(Clone, Debug, PartialEq)]
911pub struct SignMessageRequest {
912 pub message: String,
913}
914
915#[derive(Clone, Debug, PartialEq)]
917pub struct SignMessageResponse {
918 pub signature: String,
919}
920
921#[derive(Clone, Debug, PartialEq)]
923pub struct CheckMessageRequest {
924 pub message: String,
926 pub pubkey: String,
928 pub signature: String,
930}
931
932#[derive(Clone, Debug, PartialEq)]
934pub struct CheckMessageResponse {
935 pub is_valid: bool,
938}
939
940#[derive(Debug, Serialize)]
942pub struct BackupRequest {
943 pub backup_path: Option<String>,
950}
951
952#[derive(Debug, Serialize)]
954pub struct RestoreRequest {
955 pub backup_path: Option<String>,
956}
957
958#[derive(Default)]
960pub struct ListPaymentsRequest {
961 pub filters: Option<Vec<PaymentType>>,
962 pub states: Option<Vec<PaymentState>>,
963 pub from_timestamp: Option<i64>,
965 pub to_timestamp: Option<i64>,
967 pub offset: Option<u32>,
968 pub limit: Option<u32>,
969 pub details: Option<ListPaymentDetails>,
970 pub sort_ascending: Option<bool>,
971}
972
973#[derive(Debug, Serialize)]
975pub enum ListPaymentDetails {
976 Liquid {
978 asset_id: Option<String>,
980 destination: Option<String>,
982 },
983
984 Bitcoin {
986 address: Option<String>,
988 },
989}
990
991#[derive(Debug, Serialize)]
993pub enum GetPaymentRequest {
994 PaymentHash { payment_hash: String },
996 SwapId { swap_id: String },
998}
999
1000#[sdk_macros::async_trait]
1002pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1003 async fn on_bitcoin_block(&self, height: u32);
1004 async fn on_liquid_block(&self, height: u32);
1005}
1006
1007#[derive(Clone, Debug)]
1009pub enum Swap {
1010 Chain(ChainSwap),
1011 Send(SendSwap),
1012 Receive(ReceiveSwap),
1013}
1014impl Swap {
1015 pub(crate) fn id(&self) -> String {
1016 match &self {
1017 Swap::Chain(ChainSwap { id, .. })
1018 | Swap::Send(SendSwap { id, .. })
1019 | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1020 }
1021 }
1022
1023 pub(crate) fn version(&self) -> u64 {
1024 match self {
1025 Swap::Chain(ChainSwap { metadata, .. })
1026 | Swap::Send(SendSwap { metadata, .. })
1027 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1028 }
1029 }
1030
1031 pub(crate) fn set_version(&mut self, version: u64) {
1032 match self {
1033 Swap::Chain(chain_swap) => {
1034 chain_swap.metadata.version = version;
1035 }
1036 Swap::Send(send_swap) => {
1037 send_swap.metadata.version = version;
1038 }
1039 Swap::Receive(receive_swap) => {
1040 receive_swap.metadata.version = version;
1041 }
1042 }
1043 }
1044
1045 pub(crate) fn last_updated_at(&self) -> u32 {
1046 match self {
1047 Swap::Chain(ChainSwap { metadata, .. })
1048 | Swap::Send(SendSwap { metadata, .. })
1049 | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1050 }
1051 }
1052}
1053impl From<ChainSwap> for Swap {
1054 fn from(swap: ChainSwap) -> Self {
1055 Self::Chain(swap)
1056 }
1057}
1058impl From<SendSwap> for Swap {
1059 fn from(swap: SendSwap) -> Self {
1060 Self::Send(swap)
1061 }
1062}
1063impl From<ReceiveSwap> for Swap {
1064 fn from(swap: ReceiveSwap) -> Self {
1065 Self::Receive(swap)
1066 }
1067}
1068
1069#[derive(Clone, Debug)]
1070pub(crate) enum SwapScriptV2 {
1071 Bitcoin(BtcSwapScript),
1072 Liquid(LBtcSwapScript),
1073}
1074impl SwapScriptV2 {
1075 pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1076 match self {
1077 SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1078 _ => Err(anyhow!("Invalid chain")),
1079 }
1080 }
1081
1082 pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1083 match self {
1084 SwapScriptV2::Liquid(script) => Ok(script.clone()),
1085 _ => Err(anyhow!("Invalid chain")),
1086 }
1087 }
1088}
1089
1090#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1091pub enum Direction {
1092 Incoming = 0,
1093 Outgoing = 1,
1094}
1095impl ToSql for Direction {
1096 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1097 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1098 }
1099}
1100impl FromSql for Direction {
1101 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1102 match value {
1103 ValueRef::Integer(i) => match i as u8 {
1104 0 => Ok(Direction::Incoming),
1105 1 => Ok(Direction::Outgoing),
1106 _ => Err(FromSqlError::OutOfRange(i)),
1107 },
1108 _ => Err(FromSqlError::InvalidType),
1109 }
1110 }
1111}
1112
1113#[derive(Clone, Debug, Default)]
1114pub(crate) struct SwapMetadata {
1115 pub(crate) version: u64,
1117 pub(crate) last_updated_at: u32,
1118 pub(crate) is_local: bool,
1119}
1120
1121#[derive(Clone, Debug, Derivative)]
1125#[derivative(PartialEq)]
1126pub struct ChainSwap {
1127 pub(crate) id: String,
1128 pub(crate) direction: Direction,
1129 pub(crate) claim_address: Option<String>,
1132 pub(crate) lockup_address: String,
1133 pub(crate) refund_address: Option<String>,
1135 pub(crate) timeout_block_height: u32,
1136 pub(crate) preimage: String,
1137 pub(crate) description: Option<String>,
1138 pub(crate) payer_amount_sat: u64,
1140 pub(crate) actual_payer_amount_sat: Option<u64>,
1143 pub(crate) receiver_amount_sat: u64,
1145 pub(crate) accepted_receiver_amount_sat: Option<u64>,
1147 pub(crate) claim_fees_sat: u64,
1148 pub(crate) pair_fees_json: String,
1150 pub(crate) accept_zero_conf: bool,
1151 pub(crate) create_response_json: String,
1153 pub(crate) server_lockup_tx_id: Option<String>,
1155 pub(crate) user_lockup_tx_id: Option<String>,
1157 pub(crate) claim_tx_id: Option<String>,
1159 pub(crate) refund_tx_id: Option<String>,
1161 pub(crate) created_at: u32,
1162 pub(crate) state: PaymentState,
1163 pub(crate) claim_private_key: String,
1164 pub(crate) refund_private_key: String,
1165 pub(crate) auto_accepted_fees: bool,
1166 #[derivative(PartialEq = "ignore")]
1168 pub(crate) metadata: SwapMetadata,
1169}
1170impl ChainSwap {
1171 pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1172 utils::decode_keypair(&self.claim_private_key)
1173 }
1174
1175 pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1176 utils::decode_keypair(&self.refund_private_key)
1177 }
1178
1179 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1180 let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1181 serde_json::from_str(&self.create_response_json).map_err(|e| {
1182 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1183 })?;
1184
1185 Ok(CreateChainResponse {
1186 id: self.id.clone(),
1187 claim_details: internal_create_response.claim_details,
1188 lockup_details: internal_create_response.lockup_details,
1189 })
1190 }
1191
1192 pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1193 let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1194 .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1195
1196 Ok(pair)
1197 }
1198
1199 pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1200 let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1201 let our_pubkey = self.get_claim_keypair()?.public_key();
1202 let swap_script = match self.direction {
1203 Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1204 Side::Claim,
1205 chain_swap_details,
1206 our_pubkey.into(),
1207 )?),
1208 Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1209 Side::Claim,
1210 chain_swap_details,
1211 our_pubkey.into(),
1212 )?),
1213 };
1214 Ok(swap_script)
1215 }
1216
1217 pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1218 let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1219 let our_pubkey = self.get_refund_keypair()?.public_key();
1220 let swap_script = match self.direction {
1221 Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1222 Side::Lockup,
1223 chain_swap_details,
1224 our_pubkey.into(),
1225 )?),
1226 Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1227 Side::Lockup,
1228 chain_swap_details,
1229 our_pubkey.into(),
1230 )?),
1231 };
1232 Ok(swap_script)
1233 }
1234
1235 pub(crate) fn get_receive_lockup_swap_script_pubkey(
1237 &self,
1238 network: LiquidNetwork,
1239 ) -> SdkResult<ScriptBuf> {
1240 let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1241 let script_pubkey = swap_script
1242 .to_address(network.as_bitcoin_chain())
1243 .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1244 .script_pubkey();
1245 Ok(script_pubkey)
1246 }
1247
1248 pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1249 RefundableSwap {
1250 swap_address: self.lockup_address.clone(),
1251 timestamp: self.created_at,
1252 amount_sat,
1253 last_refund_tx_id: self.refund_tx_id.clone(),
1254 }
1255 }
1256
1257 pub(crate) fn from_boltz_struct_to_json(
1258 create_response: &CreateChainResponse,
1259 expected_swap_id: &str,
1260 ) -> Result<String, PaymentError> {
1261 let internal_create_response =
1262 crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1263 create_response,
1264 expected_swap_id,
1265 )?;
1266
1267 let create_response_json =
1268 serde_json::to_string(&internal_create_response).map_err(|e| {
1269 PaymentError::Generic {
1270 err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1271 }
1272 })?;
1273
1274 Ok(create_response_json)
1275 }
1276
1277 pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1278 self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1279 }
1280}
1281
1282#[derive(Clone, Debug, Default)]
1283pub(crate) struct ChainSwapUpdate {
1284 pub(crate) swap_id: String,
1285 pub(crate) to_state: PaymentState,
1286 pub(crate) server_lockup_tx_id: Option<String>,
1287 pub(crate) user_lockup_tx_id: Option<String>,
1288 pub(crate) claim_address: Option<String>,
1289 pub(crate) claim_tx_id: Option<String>,
1290 pub(crate) refund_tx_id: Option<String>,
1291}
1292
1293#[derive(Clone, Debug, Derivative)]
1295#[derivative(PartialEq)]
1296pub struct SendSwap {
1297 pub(crate) id: String,
1298 pub(crate) invoice: String,
1300 pub(crate) bolt12_offer: Option<String>,
1302 pub(crate) payment_hash: Option<String>,
1303 pub(crate) destination_pubkey: Option<String>,
1304 pub(crate) description: Option<String>,
1305 pub(crate) preimage: Option<String>,
1306 pub(crate) payer_amount_sat: u64,
1307 pub(crate) receiver_amount_sat: u64,
1308 pub(crate) pair_fees_json: String,
1310 pub(crate) create_response_json: String,
1312 pub(crate) lockup_tx_id: Option<String>,
1314 pub(crate) refund_address: Option<String>,
1316 pub(crate) refund_tx_id: Option<String>,
1318 pub(crate) created_at: u32,
1319 pub(crate) timeout_block_height: u64,
1320 pub(crate) state: PaymentState,
1321 pub(crate) refund_private_key: String,
1322 #[derivative(PartialEq = "ignore")]
1324 pub(crate) metadata: SwapMetadata,
1325}
1326impl SendSwap {
1327 pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1328 utils::decode_keypair(&self.refund_private_key)
1329 }
1330
1331 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1332 let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1333 serde_json::from_str(&self.create_response_json).map_err(|e| {
1334 anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1335 })?;
1336
1337 let res = CreateSubmarineResponse {
1338 id: self.id.clone(),
1339 accept_zero_conf: internal_create_response.accept_zero_conf,
1340 address: internal_create_response.address.clone(),
1341 bip21: internal_create_response.bip21.clone(),
1342 claim_public_key: crate::utils::json_to_pubkey(
1343 &internal_create_response.claim_public_key,
1344 )?,
1345 expected_amount: internal_create_response.expected_amount,
1346 referral_id: internal_create_response.referral_id,
1347 swap_tree: internal_create_response.swap_tree.clone().into(),
1348 timeout_block_height: internal_create_response.timeout_block_height,
1349 blinding_key: internal_create_response.blinding_key.clone(),
1350 };
1351 Ok(res)
1352 }
1353
1354 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1355 LBtcSwapScript::submarine_from_swap_resp(
1356 &self.get_boltz_create_response()?,
1357 self.get_refund_keypair()?.public_key().into(),
1358 )
1359 .map_err(|e| {
1360 SdkError::generic(format!(
1361 "Failed to create swap script for Send Swap {}: {e:?}",
1362 self.id
1363 ))
1364 })
1365 }
1366
1367 pub(crate) fn from_boltz_struct_to_json(
1368 create_response: &CreateSubmarineResponse,
1369 expected_swap_id: &str,
1370 ) -> Result<String, PaymentError> {
1371 let internal_create_response =
1372 crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1373 create_response,
1374 expected_swap_id,
1375 )?;
1376
1377 let create_response_json =
1378 serde_json::to_string(&internal_create_response).map_err(|e| {
1379 PaymentError::Generic {
1380 err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1381 }
1382 })?;
1383
1384 Ok(create_response_json)
1385 }
1386}
1387
1388#[derive(Clone, Debug, Derivative)]
1390#[derivative(PartialEq)]
1391pub struct ReceiveSwap {
1392 pub(crate) id: String,
1393 pub(crate) preimage: String,
1394 pub(crate) create_response_json: String,
1396 pub(crate) claim_private_key: String,
1397 pub(crate) invoice: String,
1398 pub(crate) bolt12_offer: Option<String>,
1400 pub(crate) payment_hash: Option<String>,
1401 pub(crate) destination_pubkey: Option<String>,
1402 pub(crate) description: Option<String>,
1403 pub(crate) payer_amount_sat: u64,
1405 pub(crate) receiver_amount_sat: u64,
1406 pub(crate) pair_fees_json: String,
1408 pub(crate) claim_fees_sat: u64,
1409 pub(crate) claim_address: Option<String>,
1411 pub(crate) claim_tx_id: Option<String>,
1413 pub(crate) lockup_tx_id: Option<String>,
1415 pub(crate) mrh_address: String,
1417 pub(crate) mrh_tx_id: Option<String>,
1419 pub(crate) created_at: u32,
1422 pub(crate) timeout_block_height: u32,
1423 pub(crate) state: PaymentState,
1424 #[derivative(PartialEq = "ignore")]
1426 pub(crate) metadata: SwapMetadata,
1427}
1428impl ReceiveSwap {
1429 pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1430 utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1431 }
1432
1433 pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1434 Ok(self
1435 .get_swap_script()?
1436 .funding_addrs
1437 .ok_or(anyhow!("No funding address found"))?
1438 .script_pubkey())
1439 }
1440
1441 pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1442 let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1443 serde_json::from_str(&self.create_response_json).map_err(|e| {
1444 PaymentError::Generic {
1445 err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1446 }
1447 })?;
1448
1449 let res = CreateReverseResponse {
1450 id: self.id.clone(),
1451 invoice: Some(self.invoice.clone()),
1452 swap_tree: internal_create_response.swap_tree.clone().into(),
1453 lockup_address: internal_create_response.lockup_address.clone(),
1454 refund_public_key: crate::utils::json_to_pubkey(
1455 &internal_create_response.refund_public_key,
1456 )?,
1457 timeout_block_height: internal_create_response.timeout_block_height,
1458 onchain_amount: internal_create_response.onchain_amount,
1459 blinding_key: internal_create_response.blinding_key.clone(),
1460 };
1461 Ok(res)
1462 }
1463
1464 pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1465 let keypair = self.get_claim_keypair()?;
1466 let create_response =
1467 self.get_boltz_create_response()
1468 .map_err(|e| PaymentError::Generic {
1469 err: format!(
1470 "Failed to create swap script for Receive Swap {}: {e:?}",
1471 self.id
1472 ),
1473 })?;
1474 LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1475 .map_err(|e| PaymentError::Generic {
1476 err: format!(
1477 "Failed to create swap script for Receive Swap {}: {e:?}",
1478 self.id
1479 ),
1480 })
1481 }
1482
1483 pub(crate) fn from_boltz_struct_to_json(
1484 create_response: &CreateReverseResponse,
1485 expected_swap_id: &str,
1486 expected_invoice: Option<&str>,
1487 ) -> Result<String, PaymentError> {
1488 let internal_create_response =
1489 crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1490 create_response,
1491 expected_swap_id,
1492 expected_invoice,
1493 )?;
1494
1495 let create_response_json =
1496 serde_json::to_string(&internal_create_response).map_err(|e| {
1497 PaymentError::Generic {
1498 err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1499 }
1500 })?;
1501
1502 Ok(create_response_json)
1503 }
1504}
1505
1506#[derive(Clone, Debug, PartialEq, Serialize)]
1508pub struct RefundableSwap {
1509 pub swap_address: String,
1510 pub timestamp: u32,
1511 pub amount_sat: u64,
1513 pub last_refund_tx_id: Option<String>,
1515}
1516
1517#[derive(Clone, Debug, Derivative)]
1519#[derivative(PartialEq)]
1520pub(crate) struct Bolt12Offer {
1521 pub(crate) id: String,
1523 pub(crate) description: String,
1525 pub(crate) private_key: String,
1527 pub(crate) webhook_url: Option<String>,
1529 pub(crate) created_at: u32,
1531}
1532impl Bolt12Offer {
1533 pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1534 utils::decode_keypair(&self.private_key)
1535 }
1536}
1537impl TryFrom<Bolt12Offer> for Offer {
1538 type Error = SdkError;
1539
1540 fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1541 Offer::from_str(&val.id)
1542 .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1543 }
1544}
1545
1546#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1548#[strum(serialize_all = "lowercase")]
1549pub enum PaymentState {
1550 #[default]
1551 Created = 0,
1552
1553 Pending = 1,
1573
1574 Complete = 2,
1586
1587 Failed = 3,
1595
1596 TimedOut = 4,
1601
1602 Refundable = 5,
1607
1608 RefundPending = 6,
1614
1615 WaitingFeeAcceptance = 7,
1627}
1628
1629impl ToSql for PaymentState {
1630 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1631 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1632 }
1633}
1634impl FromSql for PaymentState {
1635 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1636 match value {
1637 ValueRef::Integer(i) => match i as u8 {
1638 0 => Ok(PaymentState::Created),
1639 1 => Ok(PaymentState::Pending),
1640 2 => Ok(PaymentState::Complete),
1641 3 => Ok(PaymentState::Failed),
1642 4 => Ok(PaymentState::TimedOut),
1643 5 => Ok(PaymentState::Refundable),
1644 6 => Ok(PaymentState::RefundPending),
1645 7 => Ok(PaymentState::WaitingFeeAcceptance),
1646 _ => Err(FromSqlError::OutOfRange(i)),
1647 },
1648 _ => Err(FromSqlError::InvalidType),
1649 }
1650 }
1651}
1652
1653impl PaymentState {
1654 pub(crate) fn is_refundable(&self) -> bool {
1655 matches!(
1656 self,
1657 PaymentState::Refundable
1658 | PaymentState::RefundPending
1659 | PaymentState::WaitingFeeAcceptance
1660 )
1661 }
1662}
1663
1664#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1665#[strum(serialize_all = "lowercase")]
1666pub enum PaymentType {
1667 Receive = 0,
1668 Send = 1,
1669}
1670impl From<Direction> for PaymentType {
1671 fn from(value: Direction) -> Self {
1672 match value {
1673 Direction::Incoming => Self::Receive,
1674 Direction::Outgoing => Self::Send,
1675 }
1676 }
1677}
1678impl ToSql for PaymentType {
1679 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1680 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1681 }
1682}
1683impl FromSql for PaymentType {
1684 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1685 match value {
1686 ValueRef::Integer(i) => match i as u8 {
1687 0 => Ok(PaymentType::Receive),
1688 1 => Ok(PaymentType::Send),
1689 _ => Err(FromSqlError::OutOfRange(i)),
1690 },
1691 _ => Err(FromSqlError::InvalidType),
1692 }
1693 }
1694}
1695
1696#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1697pub enum PaymentStatus {
1698 Pending = 0,
1699 Complete = 1,
1700}
1701impl ToSql for PaymentStatus {
1702 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1703 Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1704 }
1705}
1706impl FromSql for PaymentStatus {
1707 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1708 match value {
1709 ValueRef::Integer(i) => match i as u8 {
1710 0 => Ok(PaymentStatus::Pending),
1711 1 => Ok(PaymentStatus::Complete),
1712 _ => Err(FromSqlError::OutOfRange(i)),
1713 },
1714 _ => Err(FromSqlError::InvalidType),
1715 }
1716 }
1717}
1718
1719#[derive(Debug, Clone, Serialize)]
1720pub struct PaymentTxData {
1721 pub tx_id: String,
1723
1724 pub timestamp: Option<u32>,
1726
1727 pub asset_id: String,
1729
1730 pub amount: u64,
1734
1735 pub fees_sat: u64,
1737
1738 pub payment_type: PaymentType,
1739
1740 pub is_confirmed: bool,
1742
1743 pub unblinding_data: Option<String>,
1746}
1747
1748#[derive(Debug, Clone, Serialize)]
1749pub enum PaymentSwapType {
1750 Receive,
1751 Send,
1752 Chain,
1753}
1754
1755#[derive(Debug, Clone, Serialize)]
1756pub struct PaymentSwapData {
1757 pub swap_id: String,
1758
1759 pub swap_type: PaymentSwapType,
1760
1761 pub created_at: u32,
1763
1764 pub expiration_blockheight: u32,
1766
1767 pub preimage: Option<String>,
1768 pub invoice: Option<String>,
1769 pub bolt12_offer: Option<String>,
1770 pub payment_hash: Option<String>,
1771 pub destination_pubkey: Option<String>,
1772 pub description: String,
1773
1774 pub payer_amount_sat: u64,
1776
1777 pub receiver_amount_sat: u64,
1779
1780 pub swapper_fees_sat: u64,
1782
1783 pub refund_tx_id: Option<String>,
1784 pub refund_tx_amount_sat: Option<u64>,
1785
1786 pub bitcoin_address: Option<String>,
1789
1790 pub status: PaymentState,
1792}
1793
1794#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1796pub struct LnUrlInfo {
1797 pub ln_address: Option<String>,
1798 pub lnurl_pay_comment: Option<String>,
1799 pub lnurl_pay_domain: Option<String>,
1800 pub lnurl_pay_metadata: Option<String>,
1801 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1802 pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1803 pub lnurl_withdraw_endpoint: Option<String>,
1804}
1805
1806#[derive(Debug, Clone, Serialize)]
1810pub struct AssetMetadata {
1811 pub asset_id: String,
1813 pub name: String,
1815 pub ticker: String,
1817 pub precision: u8,
1820 pub fiat_id: Option<String>,
1822}
1823
1824impl AssetMetadata {
1825 pub fn amount_to_sat(&self, amount: f64) -> u64 {
1826 (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1827 }
1828
1829 pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1830 amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1831 }
1832}
1833
1834#[derive(Clone, Debug, PartialEq, Serialize)]
1837pub struct AssetInfo {
1838 pub name: String,
1840 pub ticker: String,
1842 pub amount: f64,
1845 pub fees: Option<f64>,
1848}
1849
1850#[derive(Debug, Clone, PartialEq, Serialize)]
1852#[allow(clippy::large_enum_variant)]
1853pub enum PaymentDetails {
1854 Lightning {
1856 swap_id: String,
1857
1858 description: String,
1860
1861 liquid_expiration_blockheight: u32,
1863
1864 preimage: Option<String>,
1866
1867 invoice: Option<String>,
1871
1872 bolt12_offer: Option<String>,
1873
1874 payment_hash: Option<String>,
1876
1877 destination_pubkey: Option<String>,
1879
1880 lnurl_info: Option<LnUrlInfo>,
1882
1883 bip353_address: Option<String>,
1885
1886 claim_tx_id: Option<String>,
1888
1889 refund_tx_id: Option<String>,
1891
1892 refund_tx_amount_sat: Option<u64>,
1894 },
1895 Liquid {
1897 destination: String,
1899
1900 description: String,
1902
1903 asset_id: String,
1905
1906 asset_info: Option<AssetInfo>,
1908
1909 lnurl_info: Option<LnUrlInfo>,
1911
1912 bip353_address: Option<String>,
1914 },
1915 Bitcoin {
1917 swap_id: String,
1918
1919 bitcoin_address: String,
1921
1922 description: String,
1924
1925 auto_accepted_fees: bool,
1929
1930 liquid_expiration_blockheight: Option<u32>,
1933
1934 bitcoin_expiration_blockheight: Option<u32>,
1937
1938 lockup_tx_id: Option<String>,
1940
1941 claim_tx_id: Option<String>,
1943
1944 refund_tx_id: Option<String>,
1946
1947 refund_tx_amount_sat: Option<u64>,
1949 },
1950}
1951
1952impl PaymentDetails {
1953 pub(crate) fn get_swap_id(&self) -> Option<String> {
1954 match self {
1955 Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
1956 Some(swap_id.clone())
1957 }
1958 Self::Liquid { .. } => None,
1959 }
1960 }
1961
1962 pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
1963 match self {
1964 Self::Lightning {
1965 refund_tx_amount_sat,
1966 ..
1967 }
1968 | Self::Bitcoin {
1969 refund_tx_amount_sat,
1970 ..
1971 } => *refund_tx_amount_sat,
1972 Self::Liquid { .. } => None,
1973 }
1974 }
1975
1976 pub(crate) fn get_description(&self) -> Option<String> {
1977 match self {
1978 Self::Lightning { description, .. }
1979 | Self::Bitcoin { description, .. }
1980 | Self::Liquid { description, .. } => Some(description.clone()),
1981 }
1982 }
1983
1984 pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
1985 match self {
1986 Self::Liquid { asset_id, .. } => {
1987 asset_id.eq(&utils::lbtc_asset_id(network).to_string())
1988 }
1989 _ => true,
1990 }
1991 }
1992}
1993
1994#[derive(Debug, Clone, PartialEq, Serialize)]
1998pub struct Payment {
1999 pub destination: Option<String>,
2002
2003 pub tx_id: Option<String>,
2004
2005 pub unblinding_data: Option<String>,
2008
2009 pub timestamp: u32,
2015
2016 pub amount_sat: u64,
2020
2021 pub fees_sat: u64,
2035
2036 pub swapper_fees_sat: Option<u64>,
2039
2040 pub payment_type: PaymentType,
2042
2043 pub status: PaymentState,
2049
2050 pub details: PaymentDetails,
2053}
2054impl Payment {
2055 pub(crate) fn from_pending_swap(
2056 swap: PaymentSwapData,
2057 payment_type: PaymentType,
2058 payment_details: PaymentDetails,
2059 ) -> Payment {
2060 let amount_sat = match payment_type {
2061 PaymentType::Receive => swap.receiver_amount_sat,
2062 PaymentType::Send => swap.payer_amount_sat,
2063 };
2064
2065 Payment {
2066 destination: swap.invoice.clone(),
2067 tx_id: None,
2068 unblinding_data: None,
2069 timestamp: swap.created_at,
2070 amount_sat,
2071 fees_sat: swap
2072 .payer_amount_sat
2073 .saturating_sub(swap.receiver_amount_sat),
2074 swapper_fees_sat: Some(swap.swapper_fees_sat),
2075 payment_type,
2076 status: swap.status,
2077 details: payment_details,
2078 }
2079 }
2080
2081 pub(crate) fn from_tx_data(
2082 tx: PaymentTxData,
2083 swap: Option<PaymentSwapData>,
2084 details: PaymentDetails,
2085 ) -> Payment {
2086 let (amount_sat, fees_sat) = match swap.as_ref() {
2087 Some(s) => match tx.payment_type {
2088 PaymentType::Receive => (tx.amount, s.payer_amount_sat.saturating_sub(tx.amount)),
2092 PaymentType::Send => (
2093 s.receiver_amount_sat,
2094 s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2095 ),
2096 },
2097 None => {
2098 let (amount_sat, fees_sat) = match tx.payment_type {
2099 PaymentType::Receive => (tx.amount, 0),
2100 PaymentType::Send => (tx.amount, tx.fees_sat),
2101 };
2102 match details {
2105 PaymentDetails::Liquid {
2106 asset_info: Some(ref asset_info),
2107 ..
2108 } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2109 _ => (amount_sat, fees_sat),
2110 }
2111 }
2112 };
2113 Payment {
2114 tx_id: Some(tx.tx_id),
2115 unblinding_data: tx.unblinding_data,
2116 destination: match &swap {
2120 Some(PaymentSwapData {
2121 swap_type: PaymentSwapType::Receive,
2122 invoice,
2123 ..
2124 }) => invoice.clone(),
2125 Some(PaymentSwapData {
2126 swap_type: PaymentSwapType::Send,
2127 invoice,
2128 bolt12_offer,
2129 ..
2130 }) => bolt12_offer.clone().or(invoice.clone()),
2131 Some(PaymentSwapData {
2132 swap_type: PaymentSwapType::Chain,
2133 bitcoin_address,
2134 ..
2135 }) => bitcoin_address.clone(),
2136 _ => match &details {
2137 PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2138 _ => None,
2139 },
2140 },
2141 timestamp: tx
2142 .timestamp
2143 .or(swap.as_ref().map(|s| s.created_at))
2144 .unwrap_or(utils::now()),
2145 amount_sat,
2146 fees_sat,
2147 swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2148 payment_type: tx.payment_type,
2149 status: match &swap {
2150 Some(swap) => swap.status,
2151 None => match tx.is_confirmed {
2152 true => PaymentState::Complete,
2153 false => PaymentState::Pending,
2154 },
2155 },
2156 details,
2157 }
2158 }
2159
2160 pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2161 match self.details.clone() {
2162 PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2163 PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2164 PaymentDetails::Liquid { .. } => None,
2165 }
2166 .flatten()
2167 }
2168}
2169
2170#[derive(Deserialize, Serialize, Clone, Debug)]
2172#[serde(rename_all = "camelCase")]
2173pub struct RecommendedFees {
2174 pub fastest_fee: u64,
2175 pub half_hour_fee: u64,
2176 pub hour_fee: u64,
2177 pub economy_fee: u64,
2178 pub minimum_fee: u64,
2179}
2180
2181#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2183pub enum BuyBitcoinProvider {
2184 #[strum(serialize = "moonpay")]
2185 Moonpay,
2186}
2187
2188#[derive(Debug, Serialize)]
2190pub struct PrepareBuyBitcoinRequest {
2191 pub provider: BuyBitcoinProvider,
2192 pub amount_sat: u64,
2193}
2194
2195#[derive(Clone, Debug, Serialize)]
2197pub struct PrepareBuyBitcoinResponse {
2198 pub provider: BuyBitcoinProvider,
2199 pub amount_sat: u64,
2200 pub fees_sat: u64,
2201}
2202
2203#[derive(Clone, Debug, Serialize)]
2205pub struct BuyBitcoinRequest {
2206 pub prepare_response: PrepareBuyBitcoinResponse,
2207
2208 pub redirect_url: Option<String>,
2212}
2213
2214#[derive(Clone, Debug)]
2216pub struct LogEntry {
2217 pub line: String,
2218 pub level: String,
2219}
2220
2221#[derive(Clone, Debug, Serialize, Deserialize)]
2222struct InternalLeaf {
2223 pub output: String,
2224 pub version: u8,
2225}
2226impl From<InternalLeaf> for Leaf {
2227 fn from(value: InternalLeaf) -> Self {
2228 Leaf {
2229 output: value.output,
2230 version: value.version,
2231 }
2232 }
2233}
2234impl From<Leaf> for InternalLeaf {
2235 fn from(value: Leaf) -> Self {
2236 InternalLeaf {
2237 output: value.output,
2238 version: value.version,
2239 }
2240 }
2241}
2242
2243#[derive(Clone, Debug, Serialize, Deserialize)]
2244pub(super) struct InternalSwapTree {
2245 claim_leaf: InternalLeaf,
2246 refund_leaf: InternalLeaf,
2247}
2248impl From<InternalSwapTree> for SwapTree {
2249 fn from(value: InternalSwapTree) -> Self {
2250 SwapTree {
2251 claim_leaf: value.claim_leaf.into(),
2252 refund_leaf: value.refund_leaf.into(),
2253 }
2254 }
2255}
2256impl From<SwapTree> for InternalSwapTree {
2257 fn from(value: SwapTree) -> Self {
2258 InternalSwapTree {
2259 claim_leaf: value.claim_leaf.into(),
2260 refund_leaf: value.refund_leaf.into(),
2261 }
2262 }
2263}
2264
2265#[derive(Debug, Serialize)]
2267pub struct PrepareLnUrlPayRequest {
2268 pub data: LnUrlPayRequestData,
2270 pub amount: PayAmount,
2272 pub bip353_address: Option<String>,
2275 pub comment: Option<String>,
2277 pub validate_success_action_url: Option<bool>,
2280}
2281
2282#[derive(Debug, Serialize)]
2284pub struct PrepareLnUrlPayResponse {
2285 pub destination: SendDestination,
2287 pub fees_sat: u64,
2289 pub data: LnUrlPayRequestData,
2291 pub amount: PayAmount,
2293 pub comment: Option<String>,
2295 pub success_action: Option<SuccessAction>,
2298}
2299
2300#[derive(Debug, Serialize)]
2302pub struct LnUrlPayRequest {
2303 pub prepare_response: PrepareLnUrlPayResponse,
2305}
2306
2307#[derive(Serialize)]
2319#[allow(clippy::large_enum_variant)]
2320pub enum LnUrlPayResult {
2321 EndpointSuccess { data: LnUrlPaySuccessData },
2322 EndpointError { data: LnUrlErrorData },
2323 PayError { data: LnUrlPayErrorData },
2324}
2325
2326#[derive(Serialize)]
2327pub struct LnUrlPaySuccessData {
2328 pub payment: Payment,
2329 pub success_action: Option<SuccessActionProcessed>,
2330}
2331
2332#[derive(Debug, Clone)]
2333pub enum Transaction {
2334 Liquid(boltz_client::elements::Transaction),
2335 Bitcoin(boltz_client::bitcoin::Transaction),
2336}
2337
2338impl Transaction {
2339 pub(crate) fn txid(&self) -> String {
2340 match self {
2341 Transaction::Liquid(tx) => tx.txid().to_hex(),
2342 Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2343 }
2344 }
2345}
2346
2347#[derive(Debug, Clone)]
2348pub enum Utxo {
2349 Liquid(
2350 Box<(
2351 boltz_client::elements::OutPoint,
2352 boltz_client::elements::TxOut,
2353 )>,
2354 ),
2355 Bitcoin(
2356 (
2357 boltz_client::bitcoin::OutPoint,
2358 boltz_client::bitcoin::TxOut,
2359 ),
2360 ),
2361}
2362
2363impl Utxo {
2364 pub(crate) fn as_bitcoin(
2365 &self,
2366 ) -> Option<&(
2367 boltz_client::bitcoin::OutPoint,
2368 boltz_client::bitcoin::TxOut,
2369 )> {
2370 match self {
2371 Utxo::Liquid(_) => None,
2372 Utxo::Bitcoin(utxo) => Some(utxo),
2373 }
2374 }
2375
2376 pub(crate) fn as_liquid(
2377 &self,
2378 ) -> Option<
2379 Box<(
2380 boltz_client::elements::OutPoint,
2381 boltz_client::elements::TxOut,
2382 )>,
2383 > {
2384 match self {
2385 Utxo::Bitcoin(_) => None,
2386 Utxo::Liquid(utxo) => Some(utxo.clone()),
2387 }
2388 }
2389}
2390
2391#[derive(Debug, Clone)]
2393pub struct FetchPaymentProposedFeesRequest {
2394 pub swap_id: String,
2395}
2396
2397#[derive(Debug, Clone, Serialize)]
2399pub struct FetchPaymentProposedFeesResponse {
2400 pub swap_id: String,
2401 pub fees_sat: u64,
2402 pub payer_amount_sat: u64,
2404 pub receiver_amount_sat: u64,
2406}
2407
2408#[derive(Debug, Clone)]
2410pub struct AcceptPaymentProposedFeesRequest {
2411 pub response: FetchPaymentProposedFeesResponse,
2412}
2413
2414#[derive(Clone, Debug)]
2415pub struct History<T> {
2416 pub txid: T,
2417 pub height: i32,
2422}
2423pub(crate) type LBtcHistory = History<elements::Txid>;
2424pub(crate) type BtcHistory = History<bitcoin::Txid>;
2425
2426impl<T> History<T> {
2427 pub(crate) fn confirmed(&self) -> bool {
2428 self.height > 0
2429 }
2430}
2431#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2432impl From<electrum_client::GetHistoryRes> for BtcHistory {
2433 fn from(value: electrum_client::GetHistoryRes) -> Self {
2434 Self {
2435 txid: value.tx_hash,
2436 height: value.height,
2437 }
2438 }
2439}
2440impl From<lwk_wollet::History> for LBtcHistory {
2441 fn from(value: lwk_wollet::History) -> Self {
2442 Self::from(&value)
2443 }
2444}
2445impl From<&lwk_wollet::History> for LBtcHistory {
2446 fn from(value: &lwk_wollet::History) -> Self {
2447 Self {
2448 txid: value.txid,
2449 height: value.height,
2450 }
2451 }
2452}
2453pub(crate) type BtcScript = bitcoin::ScriptBuf;
2454pub(crate) type LBtcScript = elements::Script;
2455
2456#[derive(Clone, Debug)]
2457pub struct BtcScriptBalance {
2458 pub confirmed: u64,
2460 pub unconfirmed: i64,
2464}
2465#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2466impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2467 fn from(val: electrum_client::GetBalanceRes) -> Self {
2468 Self {
2469 confirmed: val.confirmed,
2470 unconfirmed: val.unconfirmed,
2471 }
2472 }
2473}
2474
2475#[macro_export]
2476macro_rules! get_updated_fields {
2477 ($($var:ident),* $(,)?) => {{
2478 let mut options = Vec::new();
2479 $(
2480 if $var.is_some() {
2481 options.push(stringify!($var).to_string());
2482 }
2483 )*
2484 match options.len() > 0 {
2485 true => Some(options),
2486 false => None,
2487 }
2488 }};
2489}