1use std::cmp::max;
2use std::ops::Add;
3use std::str::FromStr;
4
5use anyhow::{anyhow, ensure, Result};
6use chrono::{DateTime, Duration, Utc};
7use ripemd::Digest;
8use ripemd::Ripemd160;
9use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
10use rusqlite::ToSql;
11use sdk_common::grpc;
12use sdk_common::prelude::Network::*;
13use sdk_common::prelude::*;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use strum_macros::{Display, EnumString};
17
18use crate::bitcoin::blockdata::opcodes;
19use crate::bitcoin::blockdata::script::Builder;
20use crate::bitcoin::hashes::hex::{FromHex, ToHex};
21use crate::bitcoin::hashes::{sha256, Hash};
22use crate::bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
23use crate::bitcoin::{Address, Script};
24use crate::error::SdkResult;
25use crate::lsp::LspInformation;
26use crate::swap_out::boltzswap::{BoltzApiCreateReverseSwapResponse, BoltzApiReverseSwapStatus};
27use crate::swap_out::error::{ReverseSwapError, ReverseSwapResult};
28
29pub const SWAP_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60 * 24 * 2; pub const INVOICE_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60; #[derive(
34 Default, Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize, Hash,
35)]
36pub enum PaymentType {
37 #[default]
38 Sent,
39 Received,
40 ClosedChannel,
41}
42
43#[derive(Debug)]
44pub struct CustomMessage {
45 pub peer_id: Vec<u8>,
46 pub message_type: u16,
47 pub payload: Vec<u8>,
48}
49
50#[tonic::async_trait]
52pub trait LspAPI: Send + Sync {
53 async fn list_lsps(&self, node_pubkey: String) -> SdkResult<Vec<LspInformation>>;
55 async fn list_used_lsps(&self, node_pubkey: String) -> SdkResult<Vec<LspInformation>>;
57 async fn register_payment_notifications(
59 &self,
60 lsp_id: String,
61 lsp_pubkey: Vec<u8>,
62 webhook_url: String,
63 webhook_url_signature: String,
64 ) -> SdkResult<grpc::RegisterPaymentNotificationResponse>;
65
66 async fn unregister_payment_notifications(
68 &self,
69 lsp_id: String,
70 lsp_pubkey: Vec<u8>,
71 webhook_url: String,
72 webhook_url_signature: String,
73 ) -> SdkResult<grpc::RemovePaymentNotificationResponse>;
74
75 async fn register_payment(
77 &self,
78 lsp_id: String,
79 lsp_pubkey: Vec<u8>,
80 payment_info: grpc::PaymentInformation,
81 ) -> SdkResult<grpc::RegisterPaymentReply>;
82}
83
84pub struct Swap {
86 pub bitcoin_address: String,
87 pub swapper_pubkey: Vec<u8>,
88 pub lock_height: i64,
89 pub error_message: String,
90 pub required_reserve: i64,
91 pub swapper_min_payable: i64,
93 pub swapper_max_payable: i64,
95}
96
97#[tonic::async_trait]
99pub trait SwapperAPI: Send + Sync {
100 async fn complete_swap(&self, bolt11: String) -> Result<()>;
101}
102
103#[derive(Clone, PartialEq, Debug, Serialize)]
105pub struct ReverseSwapPairInfo {
106 pub min: u64,
108 pub max: u64,
110 pub fees_hash: String,
112 pub fees_percentage: f64,
114 pub fees_lockup: u64,
116 pub fees_claim: u64,
118 pub total_fees: Option<u64>,
125}
126
127#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
129pub struct FullReverseSwapInfo {
130 pub id: String,
132
133 pub created_at_block_height: u32,
135
136 pub preimage: Vec<u8>,
138
139 pub private_key: Vec<u8>,
141
142 pub claim_pubkey: String,
144
145 pub timeout_block_height: u32,
146
147 pub invoice: String,
149 pub redeem_script: String,
150
151 pub onchain_amount_sat: u64,
155
156 pub sat_per_vbyte: Option<u32>,
160
161 pub receive_amount_sat: Option<u64>,
163
164 pub cache: ReverseSwapInfoCached,
165}
166
167#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
168pub struct ReverseSwapInfoCached {
169 pub status: ReverseSwapStatus,
170 pub lockup_txid: Option<String>,
171 pub claim_txid: Option<String>,
172}
173
174impl FullReverseSwapInfo {
175 pub(crate) fn build_expected_reverse_swap_script(
177 preimage_hash: Vec<u8>,
178 compressed_pub_key: Vec<u8>,
179 sig: Vec<u8>,
180 lock_height: u32,
181 ) -> ReverseSwapResult<Script> {
182 let mut ripemd160_hasher = Ripemd160::new();
183 ripemd160_hasher.update(preimage_hash);
184 let ripemd160_hash = ripemd160_hasher.finalize();
185
186 let timeout_height_le_hex = lock_height.to_le_bytes().to_hex();
188 let timeout_height_le_hex_trimmed = timeout_height_le_hex.trim_end_matches("00");
189 let timeout_height_le_bytes = hex::decode(timeout_height_le_hex_trimmed)?;
190
191 Ok(Builder::new()
192 .push_opcode(opcodes::all::OP_SIZE)
193 .push_slice(&[0x20])
194 .push_opcode(opcodes::all::OP_EQUAL)
195 .push_opcode(opcodes::all::OP_IF)
196 .push_opcode(opcodes::all::OP_HASH160)
197 .push_slice(&ripemd160_hash[..])
198 .push_opcode(opcodes::all::OP_EQUALVERIFY)
199 .push_slice(&compressed_pub_key[..])
200 .push_opcode(opcodes::all::OP_ELSE)
201 .push_opcode(opcodes::all::OP_DROP)
202 .push_slice(&timeout_height_le_bytes)
203 .push_opcode(opcodes::all::OP_CLTV)
204 .push_opcode(opcodes::all::OP_DROP)
205 .push_slice(&sig[..])
206 .push_opcode(opcodes::all::OP_ENDIF)
207 .push_opcode(opcodes::all::OP_CHECKSIG)
208 .into_script())
209 }
210
211 pub(crate) fn validate_redeem_script(
218 &self,
219 received_lockup_address: String,
220 network: Network,
221 ) -> ReverseSwapResult<()> {
222 let redeem_script_received = Script::from_hex(&self.redeem_script)?;
223 let asm = redeem_script_received.asm();
224 debug!("received asm = {asm:?}");
225
226 let sk = SecretKey::from_slice(&self.private_key)?;
227 let pk = PublicKey::from_secret_key(&Secp256k1::new(), &sk);
228
229 let asm_elements: Vec<&str> = asm.split(' ').collect();
231 let refund_address = asm_elements.get(18).unwrap_or(&"").to_string();
232 let refund_address_bytes = hex::decode(refund_address)?;
233
234 let redeem_script_expected = Self::build_expected_reverse_swap_script(
235 self.get_preimage_hash().to_vec(), pk.serialize().to_vec(), refund_address_bytes,
238 self.timeout_block_height,
239 )?;
240 debug!("expected asm = {:?}", redeem_script_expected.asm());
241
242 match redeem_script_received.eq(&redeem_script_expected) {
243 true => {
244 let lockup_addr_expected = &received_lockup_address;
245 let lockup_addr_from_script =
246 &Address::p2wsh(&redeem_script_received, network.into()).to_string();
247
248 match lockup_addr_from_script == lockup_addr_expected {
249 true => Ok(()),
250 false => Err(ReverseSwapError::UnexpectedLockupAddress),
251 }
252 }
253 false => Err(ReverseSwapError::UnexpectedRedeemScript),
254 }
255 }
256
257 pub(crate) fn validate_invoice_amount(
258 &self,
259 expected_amount_msat: u64,
260 ) -> ReverseSwapResult<()> {
261 let inv: crate::lightning_invoice::Bolt11Invoice = self.invoice.parse()?;
262
263 let amount_from_invoice_msat = inv.amount_milli_satoshis().unwrap_or_default();
265 match amount_from_invoice_msat == expected_amount_msat {
266 false => Err(ReverseSwapError::unexpected_invoice_amount(
267 "Does not match the request",
268 )),
269 true => Ok(()),
270 }
271 }
272
273 pub(crate) fn validate_invoice(&self, expected_amount_msat: u64) -> ReverseSwapResult<()> {
279 self.validate_invoice_amount(expected_amount_msat)?;
280
281 let inv: crate::lightning_invoice::Bolt11Invoice = self.invoice.parse()?;
283 let preimage_hash_from_invoice = inv.payment_hash();
284 let preimage_hash_from_req = &self.get_preimage_hash();
285 match preimage_hash_from_invoice == preimage_hash_from_req {
286 false => Err(ReverseSwapError::unexpected_payment_hash(
287 "Does not match the request",
288 )),
289 true => Ok(()),
290 }
291 }
292
293 pub(crate) fn get_lockup_address(&self, network: Network) -> ReverseSwapResult<Address> {
295 let redeem_script = Script::from_hex(&self.redeem_script)?;
296 Ok(Address::p2wsh(&redeem_script, network.into()))
297 }
298
299 pub(crate) fn get_preimage_hash(&self) -> sha256::Hash {
301 sha256::Hash::hash(&self.preimage)
302 }
303
304 pub(crate) fn get_reverse_swap_info_using_cached_values(&self) -> ReverseSwapInfo {
306 ReverseSwapInfo {
307 id: self.id.clone(),
308 claim_pubkey: self.claim_pubkey.clone(),
309 lockup_txid: self.cache.clone().lockup_txid,
310 claim_txid: self.cache.claim_txid.clone(),
311 onchain_amount_sat: self.onchain_amount_sat,
312 status: self.cache.status,
313 }
314 }
315}
316
317#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
319pub struct ReverseSwapInfo {
320 pub id: String,
321 pub claim_pubkey: String,
322 pub lockup_txid: Option<String>,
324 pub claim_txid: Option<String>,
326 pub onchain_amount_sat: u64,
327 pub status: ReverseSwapStatus,
328}
329
330#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
334pub enum ReverseSwapStatus {
335 Initial = 0,
339
340 InProgress = 1,
343
344 Cancelled = 2,
347
348 CompletedSeen = 3,
350
351 CompletedConfirmed = 4,
353}
354
355impl ReverseSwapStatus {
356 pub(crate) fn is_monitored_state(&self) -> bool {
357 matches!(
358 self,
359 ReverseSwapStatus::Initial
360 | ReverseSwapStatus::InProgress
361 | ReverseSwapStatus::CompletedSeen
362 )
363 }
364
365 pub(crate) fn is_blocking_state(&self) -> bool {
366 matches!(
367 self,
368 ReverseSwapStatus::Initial | ReverseSwapStatus::InProgress
369 )
370 }
371}
372
373impl TryFrom<i32> for ReverseSwapStatus {
374 type Error = anyhow::Error;
375
376 fn try_from(value: i32) -> Result<Self, Self::Error> {
377 match value {
378 0 => Ok(ReverseSwapStatus::Initial),
379 1 => Ok(ReverseSwapStatus::InProgress),
380 2 => Ok(ReverseSwapStatus::Cancelled),
381 3 => Ok(ReverseSwapStatus::CompletedSeen),
382 4 => Ok(ReverseSwapStatus::CompletedConfirmed),
383 _ => Err(anyhow!("illegal value")),
384 }
385 }
386}
387
388#[tonic::async_trait]
390pub(crate) trait ReverseSwapperRoutingAPI: Send + Sync {
391 async fn fetch_reverse_routing_node(&self) -> ReverseSwapResult<Vec<u8>>;
392}
393
394#[tonic::async_trait]
395impl ReverseSwapperRoutingAPI for BreezServer {
396 async fn fetch_reverse_routing_node(&self) -> ReverseSwapResult<Vec<u8>> {
397 let mut client = self.get_swapper_client().await;
398
399 Ok(with_connection_retry!(
400 client.get_reverse_routing_node(grpc::GetReverseRoutingNodeRequest::default())
401 )
402 .await
403 .map(|reply| reply.into_inner().node_id)?)
404 }
405}
406
407#[tonic::async_trait]
409pub(crate) trait ReverseSwapServiceAPI: Send + Sync {
410 async fn fetch_reverse_swap_fees(&self) -> ReverseSwapResult<ReverseSwapPairInfo>;
413
414 async fn create_reverse_swap_on_remote(
424 &self,
425 send_amount_sat: u64,
426 preimage_hash_hex: String,
427 claim_pubkey: String,
428 pair_hash: String,
429 routing_node: String,
430 ) -> ReverseSwapResult<BoltzApiCreateReverseSwapResponse>;
431
432 async fn get_boltz_status(&self, id: String) -> ReverseSwapResult<BoltzApiReverseSwapStatus>;
434
435 async fn get_route_hints(&self, routing_node_id: String) -> ReverseSwapResult<Vec<RouteHint>>;
437}
438
439#[derive(Clone, Debug)]
441pub struct LogEntry {
442 pub line: String,
443 pub level: String,
444}
445
446#[derive(Clone)]
451pub struct Config {
452 pub breezserver: String,
453 pub chainnotifier_url: String,
454 pub mempoolspace_url: Option<String>,
461 pub working_dir: String,
464 pub network: Network,
465 pub payment_timeout_sec: u32,
466 pub default_lsp_id: Option<String>,
467 pub api_key: Option<String>,
468 pub maxfee_percent: f64,
470 pub exemptfee_msat: u64,
472 pub node_config: NodeConfig,
473}
474
475impl Config {
476 pub fn production(api_key: String, node_config: NodeConfig) -> Self {
477 Config {
478 breezserver: PRODUCTION_BREEZSERVER_URL.to_string(),
479 chainnotifier_url: "https://chainnotifier.breez.technology".to_string(),
480 mempoolspace_url: None,
481 working_dir: ".".to_string(),
482 network: Bitcoin,
483 payment_timeout_sec: 60,
484 default_lsp_id: None,
485 api_key: Some(api_key),
486 maxfee_percent: 1.0,
487 exemptfee_msat: 20000,
488 node_config,
489 }
490 }
491
492 pub fn staging(api_key: String, node_config: NodeConfig) -> Self {
493 Config {
494 breezserver: STAGING_BREEZSERVER_URL.to_string(),
495 chainnotifier_url: "https://chainnotifier.breez.technology".to_string(),
496 mempoolspace_url: None,
497 working_dir: ".".to_string(),
498 network: Bitcoin,
499 payment_timeout_sec: 60,
500 default_lsp_id: None,
501 api_key: Some(api_key),
502 maxfee_percent: 0.5,
503 exemptfee_msat: 20000,
504 node_config,
505 }
506 }
507
508 pub fn regtest(api_key: String, node_config: NodeConfig) -> Self {
509 Config {
510 breezserver: REGTEST_BREEZSERVER_URL.to_string(),
511 chainnotifier_url: "https://chainnotifier.breez.technology".to_string(),
512 mempoolspace_url: Some(REGTEST_MEMPOOL_URL.to_string()),
513 working_dir: ".".to_string(),
514 network: Regtest,
515 payment_timeout_sec: 60,
516 default_lsp_id: None,
517 api_key: Some(api_key),
518 maxfee_percent: 0.5,
519 exemptfee_msat: 20000,
520 node_config,
521 }
522 }
523}
524
525#[derive(Clone)]
526pub enum NodeConfig {
527 Greenlight { config: GreenlightNodeConfig },
528}
529
530#[derive(Clone, Serialize)]
531pub enum NodeCredentials {
532 Greenlight {
533 credentials: GreenlightDeviceCredentials,
534 },
535}
536
537#[derive(Clone)]
538pub struct GreenlightNodeConfig {
539 pub partner_credentials: Option<GreenlightCredentials>,
540 pub invite_code: Option<String>,
541}
542
543#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, EnumString)]
545pub enum EnvironmentType {
546 #[strum(serialize = "production")]
547 Production,
548 #[strum(serialize = "staging")]
549 Staging,
550 #[strum(serialize = "regtest")]
551 Regtest,
552}
553
554#[derive(Clone, Serialize, Deserialize)]
556pub struct GreenlightCredentials {
557 pub developer_key: Vec<u8>,
558 pub developer_cert: Vec<u8>,
559}
560
561#[derive(Clone, Serialize, Deserialize)]
563pub struct GreenlightDeviceCredentials {
564 pub device: Vec<u8>,
565}
566
567#[derive(Default)]
569pub struct ConfigureNodeRequest {
570 pub close_to_address: Option<String>,
571}
572
573pub struct ConnectRequest {
575 pub config: Config,
576 pub seed: Vec<u8>,
577 pub restore_only: Option<bool>,
579}
580
581#[derive(PartialEq)]
583pub enum PaymentTypeFilter {
584 Sent,
585 Received,
586 ClosedChannel,
587}
588
589pub struct MetadataFilter {
591 pub json_path: String,
593 pub json_value: String,
596}
597
598pub enum FeeratePreset {
600 Regular,
601 Economy,
602 Priority,
603}
604
605impl TryFrom<i32> for FeeratePreset {
606 type Error = anyhow::Error;
607
608 fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
609 match value {
610 0 => Ok(FeeratePreset::Regular),
611 1 => Ok(FeeratePreset::Economy),
612 2 => Ok(FeeratePreset::Priority),
613 _ => Err(anyhow!("Unexpected feerate enum value")),
614 }
615 }
616}
617
618#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
619pub struct BackupStatus {
620 pub backed_up: bool,
621 pub last_backup_time: Option<u64>,
623}
624
625#[derive(Serialize, Default, Deserialize, Clone, PartialEq, Eq, Debug)]
631pub struct NodeState {
632 pub id: String,
633 pub block_height: u32,
634 pub channels_balance_msat: u64,
635 pub onchain_balance_msat: u64,
636 #[serde(default)]
637 pub pending_onchain_balance_msat: u64,
638
639 #[serde(default)]
640 pub utxos: Vec<UnspentTransactionOutput>,
641 pub max_payable_msat: u64,
642 pub max_receivable_msat: u64,
643 pub max_single_payment_amount_msat: u64,
644 pub max_chan_reserve_msats: u64,
645 pub connected_peers: Vec<String>,
646
647 pub max_receivable_single_payment_amount_msat: u64,
649
650 pub total_inbound_liquidity_msats: u64,
652}
653
654pub struct SyncResponse {
656 pub sync_state: Value,
657 pub node_state: NodeState,
658 pub payments: Vec<crate::models::Payment>,
659 pub channels: Vec<crate::models::Channel>,
660}
661
662#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
664pub enum PaymentStatus {
665 #[default]
666 Pending = 0,
667 Complete = 1,
668 Failed = 2,
669}
670
671#[derive(Default, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
673pub struct Payment {
674 pub id: String,
675 pub payment_type: PaymentType,
676 pub payment_time: i64,
678 pub amount_msat: u64,
679 pub fee_msat: u64,
680 pub status: PaymentStatus,
681 pub error: Option<String>,
682 pub description: Option<String>,
683 pub details: PaymentDetails,
684 pub metadata: Option<String>,
685}
686
687#[derive(Default)]
689pub struct PaymentExternalInfo {
690 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
691 pub lnurl_pay_domain: Option<String>,
692 pub lnurl_pay_comment: Option<String>,
693 pub lnurl_metadata: Option<String>,
694 pub ln_address: Option<String>,
695 pub lnurl_withdraw_endpoint: Option<String>,
696 pub attempted_amount_msat: Option<u64>,
697 pub attempted_error: Option<String>,
698}
699
700#[derive(Default)]
702pub struct ListPaymentsRequest {
703 pub filters: Option<Vec<PaymentTypeFilter>>,
704 pub metadata_filters: Option<Vec<MetadataFilter>>,
705 pub from_timestamp: Option<i64>,
707 pub to_timestamp: Option<i64>,
709 pub include_failures: Option<bool>,
710 pub offset: Option<u32>,
711 pub limit: Option<u32>,
712}
713
714#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
716pub struct PaymentResponse {
717 pub payment_time: i64,
719 pub amount_msat: u64,
720 pub fee_msat: u64,
721 pub payment_hash: String,
722 pub payment_preimage: String,
723}
724
725#[allow(clippy::large_enum_variant)]
727#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
728#[serde(untagged)]
729pub enum PaymentDetails {
730 Ln {
731 #[serde(flatten)]
732 data: LnPaymentDetails,
733 },
734 ClosedChannel {
735 #[serde(flatten)]
736 data: ClosedChannelPaymentDetails,
737 },
738}
739
740impl Default for PaymentDetails {
741 fn default() -> Self {
742 Self::Ln {
743 data: LnPaymentDetails::default(),
744 }
745 }
746}
747
748impl PaymentDetails {
749 pub fn add_pending_expiration_block(&mut self, htlc: Htlc) {
750 if let PaymentDetails::Ln { data } = self {
751 data.pending_expiration_block = Some(htlc.expiry)
752 }
753 }
754}
755
756#[derive(Default, PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
758pub struct LnPaymentDetails {
759 pub payment_hash: String,
760 pub label: String,
761 pub destination_pubkey: String,
762 pub payment_preimage: String,
763 pub keysend: bool,
764 pub bolt11: String,
765
766 pub open_channel_bolt11: Option<String>,
769
770 pub lnurl_success_action: Option<SuccessActionProcessed>,
773
774 pub lnurl_pay_domain: Option<String>,
776
777 pub lnurl_pay_comment: Option<String>,
779
780 pub ln_address: Option<String>,
782
783 pub lnurl_metadata: Option<String>,
785
786 pub lnurl_withdraw_endpoint: Option<String>,
788
789 pub swap_info: Option<SwapInfo>,
791
792 pub reverse_swap_info: Option<ReverseSwapInfo>,
794
795 pub pending_expiration_block: Option<u32>,
797}
798
799#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
801pub struct ClosedChannelPaymentDetails {
802 pub state: ChannelState,
803 pub funding_txid: String,
804 pub short_channel_id: Option<String>,
805 pub closing_txid: Option<String>,
807}
808
809#[derive(Clone, Debug, Default, Serialize, Deserialize)]
810pub struct ReverseSwapFeesRequest {
811 pub send_amount_sat: Option<u64>,
813 pub claim_tx_feerate: Option<u32>,
815}
816
817#[derive(Clone, Debug, Serialize, Deserialize)]
818pub struct MaxChannelAmount {
819 pub channel_id: String,
821 pub amount_msat: u64,
823 pub path: PaymentPath,
825}
826
827#[derive(Clone, Debug, Default, Serialize, Deserialize)]
829pub struct ReceivePaymentRequest {
830 pub amount_msat: u64,
832 pub description: String,
834 pub preimage: Option<Vec<u8>>,
837 pub opening_fee_params: Option<OpeningFeeParams>,
840 pub use_description_hash: Option<bool>,
842 pub expiry: Option<u32>,
844 pub cltv: Option<u32>,
846}
847
848#[derive(Clone, Debug, Serialize, Deserialize)]
854pub struct ReceivePaymentResponse {
855 pub ln_invoice: LNInvoice,
857 pub opening_fee_params: Option<OpeningFeeParams>,
859 pub opening_fee_msat: Option<u64>,
861}
862
863#[derive(Clone, Debug, Serialize, Deserialize)]
865pub struct SendPaymentRequest {
866 pub bolt11: String,
868 pub use_trampoline: bool,
872 pub amount_msat: Option<u64>,
874 pub label: Option<String>,
876}
877
878#[derive(Clone, Debug, Serialize, Deserialize)]
880pub struct TlvEntry {
881 pub field_number: u64,
883 pub value: Vec<u8>,
885}
886
887#[derive(Clone, Debug, Serialize, Deserialize)]
889pub struct SendSpontaneousPaymentRequest {
890 pub node_id: String,
892 pub amount_msat: u64,
894 pub extra_tlvs: Option<Vec<TlvEntry>>,
896 pub label: Option<String>,
898}
899
900#[derive(Clone, Debug, Serialize, Deserialize)]
902pub struct SendPaymentResponse {
903 pub payment: Payment,
904}
905
906#[derive(Clone, Debug, Serialize, Deserialize)]
907pub struct ReportPaymentFailureDetails {
908 pub payment_hash: String,
910 pub comment: Option<String>,
912}
913
914#[derive(Clone, Debug, Serialize, Deserialize)]
916pub enum ReportIssueRequest {
917 PaymentFailure { data: ReportPaymentFailureDetails },
918}
919
920#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
922pub enum HealthCheckStatus {
923 Operational,
924 Maintenance,
925 ServiceDisruption,
926}
927
928#[derive(Clone, Debug, Serialize, Deserialize)]
930pub struct ServiceHealthCheckResponse {
931 pub status: HealthCheckStatus,
932}
933
934#[tonic::async_trait]
936pub trait SupportAPI: Send + Sync {
937 async fn service_health_check(&self) -> SdkResult<ServiceHealthCheckResponse>;
938
939 async fn report_payment_failure(
940 &self,
941 node_state: NodeState,
942 payment: Payment,
943 lsp_id: Option<String>,
944 comment: Option<String>,
945 ) -> SdkResult<()>;
946}
947
948#[derive(Clone)]
949pub struct StaticBackupRequest {
950 pub working_dir: String,
951}
952
953#[derive(Clone, Debug, Serialize, Deserialize)]
954pub struct StaticBackupResponse {
955 pub backup: Option<Vec<String>>,
956}
957
958#[derive(Default)]
959pub struct OpenChannelFeeRequest {
960 pub amount_msat: Option<u64>,
961 pub expiry: Option<u32>,
962}
963
964#[derive(Clone, Debug, Serialize, Deserialize)]
965pub struct OpenChannelFeeResponse {
966 pub fee_msat: Option<u64>,
969 pub fee_params: OpeningFeeParams,
971}
972
973#[derive(Clone, Debug, Default, Serialize, Deserialize)]
974pub struct ReceiveOnchainRequest {
975 pub opening_fee_params: Option<OpeningFeeParams>,
976}
977
978#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
979pub struct ListSwapsRequest {
980 pub status: Option<Vec<SwapStatus>>,
981 pub from_timestamp: Option<i64>,
983 pub to_timestamp: Option<i64>,
985 pub offset: Option<u32>,
986 pub limit: Option<u32>,
987}
988
989#[derive(Clone, Debug, Serialize, Deserialize)]
990pub struct BuyBitcoinRequest {
991 pub provider: BuyBitcoinProvider,
992 pub opening_fee_params: Option<OpeningFeeParams>,
993 pub redirect_url: Option<String>,
997}
998
999#[derive(Clone, Debug, Serialize, Deserialize)]
1000pub struct BuyBitcoinResponse {
1001 pub url: String,
1002 pub opening_fee_params: Option<OpeningFeeParams>,
1003}
1004
1005#[derive(Clone, Debug, Serialize, Deserialize)]
1006pub struct RedeemOnchainFundsRequest {
1007 pub to_address: String,
1008 pub sat_per_vbyte: u32,
1009}
1010
1011#[derive(Clone, Debug, Serialize, Deserialize)]
1012pub struct RedeemOnchainFundsResponse {
1013 pub txid: Vec<u8>,
1014}
1015
1016pub enum SwapAmountType {
1017 Send,
1018 Receive,
1019}
1020
1021pub struct PrepareOnchainPaymentRequest {
1023 pub amount_sat: u64,
1025 pub amount_type: SwapAmountType,
1026
1027 pub claim_tx_feerate: u32,
1029}
1030
1031#[derive(Serialize)]
1032pub struct OnchainPaymentLimitsResponse {
1033 pub min_sat: u64,
1035 pub max_sat: u64,
1037 pub max_payable_sat: u64,
1039}
1040
1041#[derive(Clone, Debug, Serialize)]
1044pub struct PrepareOnchainPaymentResponse {
1045 pub fees_hash: String,
1046 pub fees_percentage: f64,
1047 pub fees_lockup: u64,
1048 pub fees_claim: u64,
1049
1050 pub sender_amount_sat: u64,
1051 pub recipient_amount_sat: u64,
1052 pub total_fees: u64,
1053}
1054
1055#[derive(Clone, Debug)]
1056pub struct PayOnchainRequest {
1057 pub recipient_address: String,
1058 pub prepare_res: PrepareOnchainPaymentResponse,
1059}
1060
1061#[derive(Serialize)]
1062pub struct PayOnchainResponse {
1063 pub reverse_swap_info: ReverseSwapInfo,
1064}
1065
1066pub struct PrepareRefundRequest {
1067 pub swap_address: String,
1068 pub to_address: String,
1069 pub sat_per_vbyte: u32,
1070 pub unilateral: Option<bool>,
1071}
1072
1073pub struct RefundRequest {
1074 pub swap_address: String,
1075 pub to_address: String,
1076 pub sat_per_vbyte: u32,
1077 pub unilateral: Option<bool>,
1078}
1079
1080pub struct PrepareRefundResponse {
1081 pub refund_tx_weight: u32,
1082 pub refund_tx_fee_sat: u64,
1083}
1084
1085pub struct RefundResponse {
1086 pub refund_tx_id: String,
1087}
1088
1089#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1094pub struct OpeningFeeParams {
1095 pub min_msat: u64,
1097 pub proportional: u32,
1099 pub valid_until: String,
1101 pub max_idle_time: u32,
1103 pub max_client_to_self_delay: u32,
1104 pub promise: String,
1105}
1106
1107impl OpeningFeeParams {
1108 pub(crate) fn valid_until_date(&self) -> Result<DateTime<Utc>> {
1109 Ok(DateTime::parse_from_rfc3339(&self.valid_until).map(|d| d.with_timezone(&Utc))?)
1110 }
1111
1112 pub(crate) fn valid_for(&self, expiry: u32) -> Result<bool> {
1113 Ok(self.valid_until_date()? > Utc::now().add(Duration::seconds(expiry as i64)))
1114 }
1115
1116 pub(crate) fn get_channel_fees_msat_for(&self, amount_msats: u64) -> u64 {
1117 let lsp_fee_msat = amount_msats * self.proportional as u64 / 1_000_000;
1118 let lsp_fee_msat_rounded_to_sat = lsp_fee_msat / 1000 * 1000;
1119
1120 max(lsp_fee_msat_rounded_to_sat, self.min_msat)
1121 }
1122}
1123
1124impl From<OpeningFeeParams> for grpc::OpeningFeeParams {
1125 fn from(ofp: OpeningFeeParams) -> Self {
1126 Self {
1127 min_msat: ofp.min_msat,
1128 proportional: ofp.proportional,
1129 valid_until: ofp.valid_until,
1130 max_idle_time: ofp.max_idle_time,
1131 max_client_to_self_delay: ofp.max_client_to_self_delay,
1132 promise: ofp.promise,
1133 }
1134 }
1135}
1136
1137impl From<grpc::OpeningFeeParams> for OpeningFeeParams {
1138 fn from(gofp: grpc::OpeningFeeParams) -> Self {
1139 Self {
1140 min_msat: gofp.min_msat,
1141 proportional: gofp.proportional,
1142 valid_until: gofp.valid_until,
1143 max_idle_time: gofp.max_idle_time,
1144 max_client_to_self_delay: gofp.max_client_to_self_delay,
1145 promise: gofp.promise,
1146 }
1147 }
1148}
1149
1150impl FromSql for OpeningFeeParams {
1151 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1152 serde_json::from_str(value.as_str()?).map_err(|_| FromSqlError::InvalidType)
1153 }
1154}
1155
1156impl ToSql for OpeningFeeParams {
1157 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1158 Ok(ToSqlOutput::from(
1159 serde_json::to_string(&self).map_err(|_| FromSqlError::InvalidType)?,
1160 ))
1161 }
1162}
1163
1164pub enum DynamicFeeType {
1165 Cheapest,
1166 Longest,
1167}
1168
1169#[derive(Debug, Clone, Serialize, Deserialize)]
1171pub struct OpeningFeeParamsMenu {
1172 pub values: Vec<OpeningFeeParams>,
1173}
1174
1175impl OpeningFeeParamsMenu {
1176 pub fn try_from(values: Vec<sdk_common::grpc::OpeningFeeParams>) -> Result<Self> {
1182 let temp = Self {
1183 values: values
1184 .into_iter()
1185 .map(|ofp| ofp.into())
1186 .collect::<Vec<OpeningFeeParams>>(),
1187 };
1188 temp.validate().map(|_| temp)
1189 }
1190
1191 fn validate(&self) -> Result<()> {
1192 let is_ordered = self.values.windows(2).all(|ofp| {
1194 let larger_min_msat_fee = ofp[0].min_msat < ofp[1].min_msat;
1195 let equal_min_msat_fee = ofp[0].min_msat == ofp[1].min_msat;
1196
1197 let larger_proportional = ofp[0].proportional < ofp[1].proportional;
1198 let equal_proportional = ofp[0].proportional == ofp[1].proportional;
1199
1200 (larger_min_msat_fee && equal_proportional)
1201 || (equal_min_msat_fee && larger_proportional)
1202 || (larger_min_msat_fee && larger_proportional)
1203 });
1204 ensure!(is_ordered, "Validation failed: fee params are not ordered");
1205
1206 let is_expired = self.values.iter().any(|ofp| match ofp.valid_until_date() {
1208 Ok(valid_until) => Utc::now() > valid_until,
1209 Err(_) => {
1210 warn!("Failed to parse valid_until for OpeningFeeParams: {ofp:?}");
1211 false
1212 }
1213 });
1214 ensure!(!is_expired, "Validation failed: expired fee params found");
1215
1216 Ok(())
1217 }
1218
1219 pub fn get_cheapest_opening_fee_params(&self) -> Result<OpeningFeeParams> {
1220 self.values.first().cloned().ok_or_else(|| {
1221 anyhow!("The LSP doesn't support opening new channels: Dynamic fees menu contains no values")
1222 })
1223 }
1224
1225 pub fn get_48h_opening_fee_params(&self) -> Result<OpeningFeeParams> {
1226 let now = Utc::now();
1228 let duration_48h = chrono::Duration::hours(48);
1229 let valid_min_48h: Vec<OpeningFeeParams> = self
1230 .values
1231 .iter()
1232 .filter(|ofp| match ofp.valid_until_date() {
1233 Ok(valid_until) => valid_until - now > duration_48h,
1234 Err(_) => {
1235 warn!("Failed to parse valid_until for OpeningFeeParams: {ofp:?}");
1236 false
1237 }
1238 })
1239 .cloned()
1240 .collect();
1241
1242 valid_min_48h.first().cloned().ok_or_else(|| {
1245 anyhow!("The LSP doesn't support opening fees that are valid for at least 48 hours")
1246 })
1247 }
1248}
1249
1250#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
1252pub struct Channel {
1253 pub funding_txid: String,
1254 pub short_channel_id: Option<String>,
1255 pub state: ChannelState,
1256 pub spendable_msat: u64,
1257 pub local_balance_msat: u64,
1258 pub receivable_msat: u64,
1259 pub closed_at: Option<u64>,
1260 pub funding_outnum: Option<u32>,
1262 pub alias_local: Option<String>,
1263 pub alias_remote: Option<String>,
1264 pub closing_txid: Option<String>,
1268
1269 pub htlcs: Vec<Htlc>,
1270}
1271
1272#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
1273pub struct Htlc {
1274 pub expiry: u32,
1275 pub payment_hash: Vec<u8>,
1276}
1277
1278impl Htlc {
1279 pub fn from(expiry: u32, payment_hash: Vec<u8>) -> Self {
1280 Htlc {
1281 expiry,
1282 payment_hash,
1283 }
1284 }
1285}
1286
1287#[derive(Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize)]
1289pub enum ChannelState {
1290 PendingOpen,
1291 Opened,
1292 PendingClose,
1293 Closed,
1294}
1295
1296#[derive(Copy, Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
1298pub enum SwapStatus {
1299 #[default]
1303 Initial = 0,
1304
1305 WaitingConfirmation = 1,
1306
1307 Redeemable = 2,
1308
1309 Redeemed = 3,
1310
1311 Refundable = 4,
1315
1316 Completed = 5,
1317}
1318
1319impl SwapStatus {
1320 pub(crate) fn unused() -> Vec<SwapStatus> {
1321 vec![SwapStatus::Initial]
1322 }
1323
1324 pub(crate) fn in_progress() -> Vec<SwapStatus> {
1325 vec![SwapStatus::Redeemable, SwapStatus::WaitingConfirmation]
1326 }
1327
1328 pub(crate) fn redeemable() -> Vec<SwapStatus> {
1329 vec![SwapStatus::Redeemable]
1330 }
1331
1332 pub(crate) fn refundable() -> Vec<SwapStatus> {
1333 vec![SwapStatus::Refundable]
1334 }
1335
1336 pub(crate) fn monitored() -> Vec<SwapStatus> {
1337 vec![
1338 SwapStatus::Initial,
1339 SwapStatus::WaitingConfirmation,
1340 SwapStatus::Redeemable,
1341 SwapStatus::Redeemed,
1342 SwapStatus::Refundable,
1343 ]
1344 }
1345
1346 pub(crate) fn unexpired() -> Vec<SwapStatus> {
1347 vec![
1348 SwapStatus::Initial,
1349 SwapStatus::WaitingConfirmation,
1350 SwapStatus::Redeemable,
1351 SwapStatus::Redeemed,
1352 ]
1353 }
1354}
1355
1356impl TryFrom<i32> for SwapStatus {
1357 type Error = anyhow::Error;
1358
1359 fn try_from(value: i32) -> Result<Self, Self::Error> {
1360 match value {
1361 0 => Ok(SwapStatus::Initial),
1362 1 => Ok(SwapStatus::WaitingConfirmation),
1363 2 => Ok(SwapStatus::Redeemable),
1364 3 => Ok(SwapStatus::Redeemed),
1365 4 => Ok(SwapStatus::Refundable),
1366 5 => Ok(SwapStatus::Completed),
1367 _ => Err(anyhow!("illegal value")),
1368 }
1369 }
1370}
1371
1372#[derive(Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
1381pub struct SwapInfo {
1382 pub bitcoin_address: String,
1384 pub created_at: i64,
1386 pub lock_height: i64,
1388 pub payment_hash: Vec<u8>,
1390 pub preimage: Vec<u8>,
1392 pub private_key: Vec<u8>,
1394 pub public_key: Vec<u8>,
1396 pub swapper_public_key: Vec<u8>,
1398 pub script: Vec<u8>,
1400
1401 pub bolt11: Option<String>,
1403 pub paid_msat: u64,
1405 pub total_incoming_txs: u64,
1407 pub confirmed_sats: u64,
1409 pub unconfirmed_sats: u64,
1411 pub status: SwapStatus,
1413 pub refund_tx_ids: Vec<String>,
1415 pub unconfirmed_tx_ids: Vec<String>,
1417 pub confirmed_tx_ids: Vec<String>,
1419 pub min_allowed_deposit: i64,
1421 pub max_allowed_deposit: i64,
1423 pub max_swapper_payable: i64,
1425 pub last_redeem_error: Option<String>,
1427 pub channel_opening_fees: Option<OpeningFeeParams>,
1433 pub confirmed_at: Option<u32>,
1435}
1436
1437#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
1439pub struct UnspentTransactionOutput {
1440 pub txid: Vec<u8>,
1441 pub outnum: u32,
1442 pub amount_millisatoshi: u64,
1443 pub address: String,
1444 #[serde(default)]
1445 pub reserved: bool,
1446}
1447
1448#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1450#[serde(tag = "buy_bitcoin_provider")]
1451pub enum BuyBitcoinProvider {
1452 Moonpay,
1453}
1454
1455#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1459pub struct PrepareRedeemOnchainFundsRequest {
1460 pub to_address: String,
1461 pub sat_per_vbyte: u32,
1462}
1463
1464#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1467pub struct PrepareRedeemOnchainFundsResponse {
1468 pub tx_weight: u64,
1469 pub tx_fee_sat: u64,
1470}
1471
1472impl FromStr for BuyBitcoinProvider {
1473 type Err = anyhow::Error;
1474
1475 fn from_str(s: &str) -> Result<Self, Self::Err> {
1476 match s {
1477 "moonpay" => Ok(BuyBitcoinProvider::Moonpay),
1478 _ => Err(anyhow!("unknown buy bitcoin provider")),
1479 }
1480 }
1481}
1482
1483#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
1484pub struct PaymentPath {
1485 pub edges: Vec<PaymentPathEdge>,
1486}
1487
1488impl PaymentPath {
1489 pub fn final_hop_amount(&self, first_hop_amount_msat: u64) -> u64 {
1490 let mut max_to_send = first_hop_amount_msat;
1491 for h in self.edges.iter().skip(1) {
1492 max_to_send = h.amount_to_forward(max_to_send);
1493 }
1494 max_to_send
1495 }
1496
1497 pub fn first_hop_amount(&self, final_hop_amount_msat: u64) -> u64 {
1498 let mut first_hop_amount = final_hop_amount_msat;
1499 for h in self.edges.iter().skip(1).rev() {
1500 first_hop_amount = h.amount_from_forward(first_hop_amount);
1501 }
1502 first_hop_amount
1503 }
1504}
1505
1506#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
1507pub struct PaymentPathEdge {
1508 pub node_id: Vec<u8>,
1509 pub short_channel_id: String,
1510 pub channel_delay: u64,
1511 pub base_fee_msat: u64,
1512 pub fee_per_millionth: u64,
1513}
1514
1515impl PaymentPathEdge {
1516 pub(crate) fn amount_to_forward(&self, in_amount_msat: u64) -> u64 {
1517 let amount_to_forward = Self::divide_ceil(
1518 1_000_000 * (in_amount_msat - self.base_fee_msat),
1519 1_000_000 + self.fee_per_millionth,
1520 );
1521
1522 info!("amount_to_forward: in_amount_msat = {in_amount_msat},base_fee_msat={}, fee_per_millionth={} amount_to_forward: {}", self.base_fee_msat, self.fee_per_millionth, amount_to_forward);
1523 amount_to_forward
1524 }
1525
1526 pub(crate) fn amount_from_forward(&self, forward_amount_msat: u64) -> u64 {
1527 let in_amount_msat = self.base_fee_msat
1528 + forward_amount_msat * (1_000_000 + self.fee_per_millionth) / 1_000_000;
1529
1530 print!("amount_from_forward: in_amount_msat = {in_amount_msat},base_fee_msat={}, fee_per_millionth={} amount_to_forward: {}", self.base_fee_msat, self.fee_per_millionth, forward_amount_msat);
1531 in_amount_msat
1532 }
1533
1534 fn divide_ceil(dividend: u64, factor: u64) -> u64 {
1535 dividend.div_ceil(factor)
1536 }
1537}
1538
1539pub(crate) mod sanitize {
1540 use crate::{FullReverseSwapInfo, SwapInfo};
1541
1542 pub(crate) trait Sanitize {
1543 fn sanitize(self) -> Self;
1545 }
1546
1547 pub(crate) fn sanitize_vec<T>(vals: Vec<T>) -> Vec<T>
1548 where
1549 T: Sanitize,
1550 {
1551 vals.into_iter()
1552 .map(|val| val.sanitize())
1553 .collect::<Vec<T>>()
1554 }
1555
1556 impl Sanitize for FullReverseSwapInfo {
1557 fn sanitize(self) -> FullReverseSwapInfo {
1558 FullReverseSwapInfo {
1559 preimage: vec![],
1560 private_key: vec![],
1561 ..self.clone()
1562 }
1563 }
1564 }
1565
1566 impl Sanitize for SwapInfo {
1567 fn sanitize(self) -> SwapInfo {
1568 SwapInfo {
1569 preimage: vec![],
1570 private_key: vec![],
1571 ..self.clone()
1572 }
1573 }
1574 }
1575}
1576
1577#[cfg(test)]
1578mod tests {
1579 use anyhow::Result;
1580 use prost::Message;
1581 use rand::random;
1582 use sdk_common::grpc;
1583
1584 use crate::models::sanitize::Sanitize;
1585 use crate::test_utils::{get_test_ofp, get_test_ofp_48h, rand_string, rand_vec_u8};
1586 use crate::{
1587 FullReverseSwapInfo, OpeningFeeParams, PaymentPath, PaymentPathEdge, ReverseSwapInfoCached,
1588 ReverseSwapStatus, SwapInfo,
1589 };
1590
1591 #[test]
1592 fn test_route_fees() -> Result<()> {
1593 let route = PaymentPath {
1594 edges: vec![
1595 PaymentPathEdge {
1596 node_id: vec![1],
1597 short_channel_id: "807189x2048x0".into(),
1598 channel_delay: 34,
1599 base_fee_msat: 1000,
1600 fee_per_millionth: 10,
1601 },
1602 PaymentPathEdge {
1603 node_id: vec![2],
1604 short_channel_id: "811871x2726x1".into(),
1605 channel_delay: 34,
1606 base_fee_msat: 0,
1607 fee_per_millionth: 0,
1608 },
1609 PaymentPathEdge {
1610 node_id: vec![3],
1611 short_channel_id: "16000000x0x18087".into(),
1612 channel_delay: 40,
1613 base_fee_msat: 1000,
1614 fee_per_millionth: 1,
1615 },
1616 ],
1617 };
1618 assert_eq!(route.final_hop_amount(1141000), 1139999);
1619 assert_eq!(route.first_hop_amount(1139999), 1141000);
1620
1621 let route = PaymentPath {
1622 edges: vec![
1623 PaymentPathEdge {
1624 node_id: vec![1],
1625 short_channel_id: "807189x2048x0".into(),
1626 channel_delay: 34,
1627 base_fee_msat: 1000,
1628 fee_per_millionth: 10,
1629 },
1630 PaymentPathEdge {
1631 node_id: vec![2],
1632 short_channel_id: "811871x2726x1".into(),
1633 channel_delay: 34,
1634 base_fee_msat: 0,
1635 fee_per_millionth: 0,
1636 },
1637 PaymentPathEdge {
1638 node_id: vec![3],
1639 short_channel_id: "16000000x0x18087".into(),
1640 channel_delay: 40,
1641 base_fee_msat: 0,
1642 fee_per_millionth: 2000,
1643 },
1644 ],
1645 };
1646 assert_eq!(route.final_hop_amount(1141314), 1139036);
1647 assert_eq!(route.first_hop_amount(1139036), 1141314);
1648
1649 Ok(())
1650 }
1651
1652 use super::OpeningFeeParamsMenu;
1653
1654 #[test]
1655 fn test_ofp_menu_validation() -> Result<()> {
1656 OpeningFeeParamsMenu::try_from(vec![get_test_ofp(10, 12, true)])?;
1658
1659 assert!(OpeningFeeParamsMenu::try_from(vec![
1661 get_test_ofp(10, 12, true),
1662 get_test_ofp(10, 12, true),
1663 ])
1664 .is_err());
1665
1666 OpeningFeeParamsMenu::try_from(vec![
1668 get_test_ofp(10, 12, true),
1669 get_test_ofp(12, 12, true),
1670 ])?;
1671
1672 OpeningFeeParamsMenu::try_from(vec![
1674 get_test_ofp(10, 12, true),
1675 get_test_ofp(10, 14, true),
1676 ])?;
1677
1678 OpeningFeeParamsMenu::try_from(vec![
1680 get_test_ofp(10, 12, true),
1681 get_test_ofp(12, 14, true),
1682 ])?;
1683
1684 assert!(OpeningFeeParamsMenu::try_from(vec![
1687 get_test_ofp(10, 12, true),
1688 get_test_ofp(10, 12, true),
1689 ])
1690 .is_err());
1691 assert!(OpeningFeeParamsMenu::try_from(vec![
1693 get_test_ofp(10, 12, true),
1694 get_test_ofp(10, 10, true),
1695 ])
1696 .is_err());
1697 assert!(OpeningFeeParamsMenu::try_from(vec![
1699 get_test_ofp(12, 10, true),
1700 get_test_ofp(10, 10, true),
1701 ])
1702 .is_err());
1703
1704 assert!(OpeningFeeParamsMenu::try_from(vec![
1706 get_test_ofp(10, 10, true),
1707 get_test_ofp(12, 12, false),
1708 ])
1709 .is_err());
1710
1711 assert!(OpeningFeeParamsMenu::try_from(vec![
1713 get_test_ofp(10, 10, false),
1714 get_test_ofp(12, 12, false),
1715 ])
1716 .is_err());
1717
1718 Ok(())
1719 }
1720
1721 #[test]
1722 fn test_payment_information_ser_de() -> Result<()> {
1723 let dummy_payment_info = grpc::PaymentInformation {
1724 payment_hash: rand_vec_u8(10),
1725 payment_secret: rand_vec_u8(10),
1726 destination: rand_vec_u8(10),
1727 incoming_amount_msat: random(),
1728 outgoing_amount_msat: random(),
1729 tag: "".to_string(),
1730 opening_fee_params: None,
1731 };
1732
1733 let mut buf = Vec::with_capacity(dummy_payment_info.encoded_len());
1734 dummy_payment_info.encode(&mut buf)?;
1735
1736 let decoded_payment_info = grpc::PaymentInformation::decode(&*buf)?;
1737
1738 assert_eq!(dummy_payment_info, decoded_payment_info);
1739
1740 Ok(())
1741 }
1742
1743 #[test]
1744 fn test_dynamic_fee_valid_until_format() -> Result<()> {
1745 let mut ofp: OpeningFeeParams = get_test_ofp(1, 1, true).into();
1746 ofp.valid_until = "2023-08-03T00:30:35.117Z".to_string();
1747 ofp.valid_until_date().map(|_| ())
1748 }
1749
1750 #[test]
1752 fn test_sanitization() -> Result<()> {
1753 let rev_swap_info_sanitized = FullReverseSwapInfo {
1754 id: "rev_swap_id".to_string(),
1755 created_at_block_height: 0,
1756 preimage: rand_vec_u8(32),
1757 private_key: vec![],
1758 claim_pubkey: "claim_pubkey".to_string(),
1759 timeout_block_height: 600_000,
1760 invoice: "645".to_string(),
1761 redeem_script: "redeem_script".to_string(),
1762 onchain_amount_sat: 250,
1763 sat_per_vbyte: Some(50),
1764 receive_amount_sat: None,
1765 cache: ReverseSwapInfoCached {
1766 status: ReverseSwapStatus::CompletedConfirmed,
1767 lockup_txid: Some("lockup_txid".to_string()),
1768 claim_txid: Some("claim_txid".to_string()),
1769 },
1770 }
1771 .sanitize();
1772 assert_eq!(rev_swap_info_sanitized.preimage, Vec::<u8>::new());
1773 assert_eq!(rev_swap_info_sanitized.private_key, Vec::<u8>::new());
1774
1775 let swap_info_sanitized = SwapInfo {
1776 bitcoin_address: rand_string(10),
1777 created_at: 10,
1778 lock_height: random(),
1779 payment_hash: rand_vec_u8(32),
1780 preimage: rand_vec_u8(32),
1781 private_key: rand_vec_u8(32),
1782 public_key: rand_vec_u8(10),
1783 swapper_public_key: rand_vec_u8(10),
1784 script: rand_vec_u8(10),
1785 bolt11: None,
1786 paid_msat: 0,
1787 unconfirmed_sats: 0,
1788 confirmed_sats: 0,
1789 total_incoming_txs: 0,
1790 status: crate::models::SwapStatus::Initial,
1791 refund_tx_ids: Vec::new(),
1792 unconfirmed_tx_ids: Vec::new(),
1793 confirmed_tx_ids: Vec::new(),
1794 min_allowed_deposit: 0,
1795 max_allowed_deposit: 100,
1796 max_swapper_payable: 200,
1797 last_redeem_error: None,
1798 channel_opening_fees: Some(get_test_ofp_48h(random(), random()).into()),
1799 confirmed_at: None,
1800 }
1801 .sanitize();
1802 assert_eq!(swap_info_sanitized.preimage, Vec::<u8>::new());
1803 assert_eq!(swap_info_sanitized.private_key, Vec::<u8>::new());
1804
1805 Ok(())
1806 }
1807}