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
509#[derive(Clone)]
510pub enum NodeConfig {
511 Greenlight { config: GreenlightNodeConfig },
512}
513
514#[derive(Clone, Serialize)]
515pub enum NodeCredentials {
516 Greenlight {
517 credentials: GreenlightDeviceCredentials,
518 },
519}
520
521#[derive(Clone)]
522pub struct GreenlightNodeConfig {
523 pub partner_credentials: Option<GreenlightCredentials>,
524 pub invite_code: Option<String>,
525}
526
527#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, EnumString)]
529pub enum EnvironmentType {
530 #[strum(serialize = "production")]
531 Production,
532 #[strum(serialize = "staging")]
533 Staging,
534}
535
536#[derive(Clone, Serialize, Deserialize)]
538pub struct GreenlightCredentials {
539 pub developer_key: Vec<u8>,
540 pub developer_cert: Vec<u8>,
541}
542
543#[derive(Clone, Serialize, Deserialize)]
545pub struct GreenlightDeviceCredentials {
546 pub device: Vec<u8>,
547}
548
549#[derive(Default)]
551pub struct ConfigureNodeRequest {
552 pub close_to_address: Option<String>,
553}
554
555pub struct ConnectRequest {
557 pub config: Config,
558 pub seed: Vec<u8>,
559 pub restore_only: Option<bool>,
561}
562
563#[derive(PartialEq)]
565pub enum PaymentTypeFilter {
566 Sent,
567 Received,
568 ClosedChannel,
569}
570
571pub struct MetadataFilter {
573 pub json_path: String,
575 pub json_value: String,
578}
579
580pub enum FeeratePreset {
582 Regular,
583 Economy,
584 Priority,
585}
586
587impl TryFrom<i32> for FeeratePreset {
588 type Error = anyhow::Error;
589
590 fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
591 match value {
592 0 => Ok(FeeratePreset::Regular),
593 1 => Ok(FeeratePreset::Economy),
594 2 => Ok(FeeratePreset::Priority),
595 _ => Err(anyhow!("Unexpected feerate enum value")),
596 }
597 }
598}
599
600#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
601pub struct BackupStatus {
602 pub backed_up: bool,
603 pub last_backup_time: Option<u64>,
605}
606
607#[derive(Serialize, Default, Deserialize, Clone, PartialEq, Eq, Debug)]
613pub struct NodeState {
614 pub id: String,
615 pub block_height: u32,
616 pub channels_balance_msat: u64,
617 pub onchain_balance_msat: u64,
618 #[serde(default)]
619 pub pending_onchain_balance_msat: u64,
620
621 #[serde(default)]
622 pub utxos: Vec<UnspentTransactionOutput>,
623 pub max_payable_msat: u64,
624 pub max_receivable_msat: u64,
625 pub max_single_payment_amount_msat: u64,
626 pub max_chan_reserve_msats: u64,
627 pub connected_peers: Vec<String>,
628
629 pub max_receivable_single_payment_amount_msat: u64,
631
632 pub total_inbound_liquidity_msats: u64,
634}
635
636pub struct SyncResponse {
638 pub sync_state: Value,
639 pub node_state: NodeState,
640 pub payments: Vec<crate::models::Payment>,
641 pub channels: Vec<crate::models::Channel>,
642}
643
644#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
646pub enum PaymentStatus {
647 #[default]
648 Pending = 0,
649 Complete = 1,
650 Failed = 2,
651}
652
653#[derive(Default, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
655pub struct Payment {
656 pub id: String,
657 pub payment_type: PaymentType,
658 pub payment_time: i64,
660 pub amount_msat: u64,
661 pub fee_msat: u64,
662 pub status: PaymentStatus,
663 pub error: Option<String>,
664 pub description: Option<String>,
665 pub details: PaymentDetails,
666 pub metadata: Option<String>,
667}
668
669#[derive(Default)]
671pub struct PaymentExternalInfo {
672 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
673 pub lnurl_pay_domain: Option<String>,
674 pub lnurl_pay_comment: Option<String>,
675 pub lnurl_metadata: Option<String>,
676 pub ln_address: Option<String>,
677 pub lnurl_withdraw_endpoint: Option<String>,
678 pub attempted_amount_msat: Option<u64>,
679 pub attempted_error: Option<String>,
680}
681
682#[derive(Default)]
684pub struct ListPaymentsRequest {
685 pub filters: Option<Vec<PaymentTypeFilter>>,
686 pub metadata_filters: Option<Vec<MetadataFilter>>,
687 pub from_timestamp: Option<i64>,
689 pub to_timestamp: Option<i64>,
691 pub include_failures: Option<bool>,
692 pub offset: Option<u32>,
693 pub limit: Option<u32>,
694}
695
696#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
698pub struct PaymentResponse {
699 pub payment_time: i64,
701 pub amount_msat: u64,
702 pub fee_msat: u64,
703 pub payment_hash: String,
704 pub payment_preimage: String,
705}
706
707#[allow(clippy::large_enum_variant)]
709#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
710#[serde(untagged)]
711pub enum PaymentDetails {
712 Ln {
713 #[serde(flatten)]
714 data: LnPaymentDetails,
715 },
716 ClosedChannel {
717 #[serde(flatten)]
718 data: ClosedChannelPaymentDetails,
719 },
720}
721
722impl Default for PaymentDetails {
723 fn default() -> Self {
724 Self::Ln {
725 data: LnPaymentDetails::default(),
726 }
727 }
728}
729
730impl PaymentDetails {
731 pub fn add_pending_expiration_block(&mut self, htlc: Htlc) {
732 if let PaymentDetails::Ln { data } = self {
733 data.pending_expiration_block = Some(htlc.expiry)
734 }
735 }
736}
737
738#[derive(Default, PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
740pub struct LnPaymentDetails {
741 pub payment_hash: String,
742 pub label: String,
743 pub destination_pubkey: String,
744 pub payment_preimage: String,
745 pub keysend: bool,
746 pub bolt11: String,
747
748 pub open_channel_bolt11: Option<String>,
751
752 pub lnurl_success_action: Option<SuccessActionProcessed>,
755
756 pub lnurl_pay_domain: Option<String>,
758
759 pub lnurl_pay_comment: Option<String>,
761
762 pub ln_address: Option<String>,
764
765 pub lnurl_metadata: Option<String>,
767
768 pub lnurl_withdraw_endpoint: Option<String>,
770
771 pub swap_info: Option<SwapInfo>,
773
774 pub reverse_swap_info: Option<ReverseSwapInfo>,
776
777 pub pending_expiration_block: Option<u32>,
779}
780
781#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
783pub struct ClosedChannelPaymentDetails {
784 pub state: ChannelState,
785 pub funding_txid: String,
786 pub short_channel_id: Option<String>,
787 pub closing_txid: Option<String>,
789}
790
791#[derive(Clone, Debug, Default, Serialize, Deserialize)]
792pub struct ReverseSwapFeesRequest {
793 pub send_amount_sat: Option<u64>,
795 pub claim_tx_feerate: Option<u32>,
797}
798
799#[derive(Clone, Debug, Serialize, Deserialize)]
800pub struct MaxChannelAmount {
801 pub channel_id: String,
803 pub amount_msat: u64,
805 pub path: PaymentPath,
807}
808
809#[derive(Clone, Debug, Default, Serialize, Deserialize)]
811pub struct ReceivePaymentRequest {
812 pub amount_msat: u64,
814 pub description: String,
816 pub preimage: Option<Vec<u8>>,
819 pub opening_fee_params: Option<OpeningFeeParams>,
822 pub use_description_hash: Option<bool>,
824 pub expiry: Option<u32>,
826 pub cltv: Option<u32>,
828}
829
830#[derive(Clone, Debug, Serialize, Deserialize)]
836pub struct ReceivePaymentResponse {
837 pub ln_invoice: LNInvoice,
839 pub opening_fee_params: Option<OpeningFeeParams>,
841 pub opening_fee_msat: Option<u64>,
843}
844
845#[derive(Clone, Debug, Serialize, Deserialize)]
847pub struct SendPaymentRequest {
848 pub bolt11: String,
850 pub use_trampoline: bool,
854 pub amount_msat: Option<u64>,
856 pub label: Option<String>,
858}
859
860#[derive(Clone, Debug, Serialize, Deserialize)]
862pub struct TlvEntry {
863 pub field_number: u64,
865 pub value: Vec<u8>,
867}
868
869#[derive(Clone, Debug, Serialize, Deserialize)]
871pub struct SendSpontaneousPaymentRequest {
872 pub node_id: String,
874 pub amount_msat: u64,
876 pub extra_tlvs: Option<Vec<TlvEntry>>,
878 pub label: Option<String>,
880}
881
882#[derive(Clone, Debug, Serialize, Deserialize)]
884pub struct SendPaymentResponse {
885 pub payment: Payment,
886}
887
888#[derive(Clone, Debug, Serialize, Deserialize)]
889pub struct ReportPaymentFailureDetails {
890 pub payment_hash: String,
892 pub comment: Option<String>,
894}
895
896#[derive(Clone, Debug, Serialize, Deserialize)]
898pub enum ReportIssueRequest {
899 PaymentFailure { data: ReportPaymentFailureDetails },
900}
901
902#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
904pub enum HealthCheckStatus {
905 Operational,
906 Maintenance,
907 ServiceDisruption,
908}
909
910#[derive(Clone, Debug, Serialize, Deserialize)]
912pub struct ServiceHealthCheckResponse {
913 pub status: HealthCheckStatus,
914}
915
916#[tonic::async_trait]
918pub trait SupportAPI: Send + Sync {
919 async fn service_health_check(&self) -> SdkResult<ServiceHealthCheckResponse>;
920
921 async fn report_payment_failure(
922 &self,
923 node_state: NodeState,
924 payment: Payment,
925 lsp_id: Option<String>,
926 comment: Option<String>,
927 ) -> SdkResult<()>;
928}
929
930#[derive(Clone)]
931pub struct StaticBackupRequest {
932 pub working_dir: String,
933}
934
935#[derive(Clone, Debug, Serialize, Deserialize)]
936pub struct StaticBackupResponse {
937 pub backup: Option<Vec<String>>,
938}
939
940#[derive(Default)]
941pub struct OpenChannelFeeRequest {
942 pub amount_msat: Option<u64>,
943 pub expiry: Option<u32>,
944}
945
946#[derive(Clone, Debug, Serialize, Deserialize)]
947pub struct OpenChannelFeeResponse {
948 pub fee_msat: Option<u64>,
951 pub fee_params: OpeningFeeParams,
953}
954
955#[derive(Clone, Debug, Default, Serialize, Deserialize)]
956pub struct ReceiveOnchainRequest {
957 pub opening_fee_params: Option<OpeningFeeParams>,
958}
959
960#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
961pub struct ListSwapsRequest {
962 pub status: Option<Vec<SwapStatus>>,
963 pub from_timestamp: Option<i64>,
965 pub to_timestamp: Option<i64>,
967 pub offset: Option<u32>,
968 pub limit: Option<u32>,
969}
970
971#[derive(Clone, Debug, Serialize, Deserialize)]
972pub struct BuyBitcoinRequest {
973 pub provider: BuyBitcoinProvider,
974 pub opening_fee_params: Option<OpeningFeeParams>,
975 pub redirect_url: Option<String>,
979}
980
981#[derive(Clone, Debug, Serialize, Deserialize)]
982pub struct BuyBitcoinResponse {
983 pub url: String,
984 pub opening_fee_params: Option<OpeningFeeParams>,
985}
986
987#[derive(Clone, Debug, Serialize, Deserialize)]
988pub struct RedeemOnchainFundsRequest {
989 pub to_address: String,
990 pub sat_per_vbyte: u32,
991}
992
993#[derive(Clone, Debug, Serialize, Deserialize)]
994pub struct RedeemOnchainFundsResponse {
995 pub txid: Vec<u8>,
996}
997
998pub enum SwapAmountType {
999 Send,
1000 Receive,
1001}
1002
1003pub struct PrepareOnchainPaymentRequest {
1005 pub amount_sat: u64,
1007 pub amount_type: SwapAmountType,
1008
1009 pub claim_tx_feerate: u32,
1011}
1012
1013#[derive(Serialize)]
1014pub struct OnchainPaymentLimitsResponse {
1015 pub min_sat: u64,
1017 pub max_sat: u64,
1019 pub max_payable_sat: u64,
1021}
1022
1023#[derive(Clone, Debug, Serialize)]
1026pub struct PrepareOnchainPaymentResponse {
1027 pub fees_hash: String,
1028 pub fees_percentage: f64,
1029 pub fees_lockup: u64,
1030 pub fees_claim: u64,
1031
1032 pub sender_amount_sat: u64,
1033 pub recipient_amount_sat: u64,
1034 pub total_fees: u64,
1035}
1036
1037#[derive(Clone, Debug)]
1038pub struct PayOnchainRequest {
1039 pub recipient_address: String,
1040 pub prepare_res: PrepareOnchainPaymentResponse,
1041}
1042
1043#[derive(Serialize)]
1044pub struct PayOnchainResponse {
1045 pub reverse_swap_info: ReverseSwapInfo,
1046}
1047
1048pub struct PrepareRefundRequest {
1049 pub swap_address: String,
1050 pub to_address: String,
1051 pub sat_per_vbyte: u32,
1052 pub unilateral: Option<bool>,
1053}
1054
1055pub struct RefundRequest {
1056 pub swap_address: String,
1057 pub to_address: String,
1058 pub sat_per_vbyte: u32,
1059 pub unilateral: Option<bool>,
1060}
1061
1062pub struct PrepareRefundResponse {
1063 pub refund_tx_weight: u32,
1064 pub refund_tx_fee_sat: u64,
1065}
1066
1067pub struct RefundResponse {
1068 pub refund_tx_id: String,
1069}
1070
1071#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1076pub struct OpeningFeeParams {
1077 pub min_msat: u64,
1079 pub proportional: u32,
1081 pub valid_until: String,
1083 pub max_idle_time: u32,
1085 pub max_client_to_self_delay: u32,
1086 pub promise: String,
1087}
1088
1089impl OpeningFeeParams {
1090 pub(crate) fn valid_until_date(&self) -> Result<DateTime<Utc>> {
1091 Ok(DateTime::parse_from_rfc3339(&self.valid_until).map(|d| d.with_timezone(&Utc))?)
1092 }
1093
1094 pub(crate) fn valid_for(&self, expiry: u32) -> Result<bool> {
1095 Ok(self.valid_until_date()? > Utc::now().add(Duration::seconds(expiry as i64)))
1096 }
1097
1098 pub(crate) fn get_channel_fees_msat_for(&self, amount_msats: u64) -> u64 {
1099 let lsp_fee_msat = amount_msats * self.proportional as u64 / 1_000_000;
1100 let lsp_fee_msat_rounded_to_sat = lsp_fee_msat / 1000 * 1000;
1101
1102 max(lsp_fee_msat_rounded_to_sat, self.min_msat)
1103 }
1104}
1105
1106impl From<OpeningFeeParams> for grpc::OpeningFeeParams {
1107 fn from(ofp: OpeningFeeParams) -> Self {
1108 Self {
1109 min_msat: ofp.min_msat,
1110 proportional: ofp.proportional,
1111 valid_until: ofp.valid_until,
1112 max_idle_time: ofp.max_idle_time,
1113 max_client_to_self_delay: ofp.max_client_to_self_delay,
1114 promise: ofp.promise,
1115 }
1116 }
1117}
1118
1119impl From<grpc::OpeningFeeParams> for OpeningFeeParams {
1120 fn from(gofp: grpc::OpeningFeeParams) -> Self {
1121 Self {
1122 min_msat: gofp.min_msat,
1123 proportional: gofp.proportional,
1124 valid_until: gofp.valid_until,
1125 max_idle_time: gofp.max_idle_time,
1126 max_client_to_self_delay: gofp.max_client_to_self_delay,
1127 promise: gofp.promise,
1128 }
1129 }
1130}
1131
1132impl FromSql for OpeningFeeParams {
1133 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1134 serde_json::from_str(value.as_str()?).map_err(|_| FromSqlError::InvalidType)
1135 }
1136}
1137
1138impl ToSql for OpeningFeeParams {
1139 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1140 Ok(ToSqlOutput::from(
1141 serde_json::to_string(&self).map_err(|_| FromSqlError::InvalidType)?,
1142 ))
1143 }
1144}
1145
1146pub enum DynamicFeeType {
1147 Cheapest,
1148 Longest,
1149}
1150
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1153pub struct OpeningFeeParamsMenu {
1154 pub values: Vec<OpeningFeeParams>,
1155}
1156
1157impl OpeningFeeParamsMenu {
1158 pub fn try_from(values: Vec<sdk_common::grpc::OpeningFeeParams>) -> Result<Self> {
1164 let temp = Self {
1165 values: values
1166 .into_iter()
1167 .map(|ofp| ofp.into())
1168 .collect::<Vec<OpeningFeeParams>>(),
1169 };
1170 temp.validate().map(|_| temp)
1171 }
1172
1173 fn validate(&self) -> Result<()> {
1174 let is_ordered = self.values.windows(2).all(|ofp| {
1176 let larger_min_msat_fee = ofp[0].min_msat < ofp[1].min_msat;
1177 let equal_min_msat_fee = ofp[0].min_msat == ofp[1].min_msat;
1178
1179 let larger_proportional = ofp[0].proportional < ofp[1].proportional;
1180 let equal_proportional = ofp[0].proportional == ofp[1].proportional;
1181
1182 (larger_min_msat_fee && equal_proportional)
1183 || (equal_min_msat_fee && larger_proportional)
1184 || (larger_min_msat_fee && larger_proportional)
1185 });
1186 ensure!(is_ordered, "Validation failed: fee params are not ordered");
1187
1188 let is_expired = self.values.iter().any(|ofp| match ofp.valid_until_date() {
1190 Ok(valid_until) => Utc::now() > valid_until,
1191 Err(_) => {
1192 warn!("Failed to parse valid_until for OpeningFeeParams: {ofp:?}");
1193 false
1194 }
1195 });
1196 ensure!(!is_expired, "Validation failed: expired fee params found");
1197
1198 Ok(())
1199 }
1200
1201 pub fn get_cheapest_opening_fee_params(&self) -> Result<OpeningFeeParams> {
1202 self.values.first().cloned().ok_or_else(|| {
1203 anyhow!("The LSP doesn't support opening new channels: Dynamic fees menu contains no values")
1204 })
1205 }
1206
1207 pub fn get_48h_opening_fee_params(&self) -> Result<OpeningFeeParams> {
1208 let now = Utc::now();
1210 let duration_48h = chrono::Duration::hours(48);
1211 let valid_min_48h: Vec<OpeningFeeParams> = self
1212 .values
1213 .iter()
1214 .filter(|ofp| match ofp.valid_until_date() {
1215 Ok(valid_until) => valid_until - now > duration_48h,
1216 Err(_) => {
1217 warn!("Failed to parse valid_until for OpeningFeeParams: {ofp:?}");
1218 false
1219 }
1220 })
1221 .cloned()
1222 .collect();
1223
1224 valid_min_48h.first().cloned().ok_or_else(|| {
1227 anyhow!("The LSP doesn't support opening fees that are valid for at least 48 hours")
1228 })
1229 }
1230}
1231
1232#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
1234pub struct Channel {
1235 pub funding_txid: String,
1236 pub short_channel_id: Option<String>,
1237 pub state: ChannelState,
1238 pub spendable_msat: u64,
1239 pub local_balance_msat: u64,
1240 pub receivable_msat: u64,
1241 pub closed_at: Option<u64>,
1242 pub funding_outnum: Option<u32>,
1244 pub alias_local: Option<String>,
1245 pub alias_remote: Option<String>,
1246 pub closing_txid: Option<String>,
1250
1251 pub htlcs: Vec<Htlc>,
1252}
1253
1254#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
1255pub struct Htlc {
1256 pub expiry: u32,
1257 pub payment_hash: Vec<u8>,
1258}
1259
1260impl Htlc {
1261 pub fn from(expiry: u32, payment_hash: Vec<u8>) -> Self {
1262 Htlc {
1263 expiry,
1264 payment_hash,
1265 }
1266 }
1267}
1268
1269#[derive(Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize)]
1271pub enum ChannelState {
1272 PendingOpen,
1273 Opened,
1274 PendingClose,
1275 Closed,
1276}
1277
1278#[derive(Copy, Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
1280pub enum SwapStatus {
1281 #[default]
1285 Initial = 0,
1286
1287 WaitingConfirmation = 1,
1288
1289 Redeemable = 2,
1290
1291 Redeemed = 3,
1292
1293 Refundable = 4,
1297
1298 Completed = 5,
1299}
1300
1301impl SwapStatus {
1302 pub(crate) fn unused() -> Vec<SwapStatus> {
1303 vec![SwapStatus::Initial]
1304 }
1305
1306 pub(crate) fn in_progress() -> Vec<SwapStatus> {
1307 vec![SwapStatus::Redeemable, SwapStatus::WaitingConfirmation]
1308 }
1309
1310 pub(crate) fn redeemable() -> Vec<SwapStatus> {
1311 vec![SwapStatus::Redeemable]
1312 }
1313
1314 pub(crate) fn refundable() -> Vec<SwapStatus> {
1315 vec![SwapStatus::Refundable]
1316 }
1317
1318 pub(crate) fn monitored() -> Vec<SwapStatus> {
1319 vec![
1320 SwapStatus::Initial,
1321 SwapStatus::WaitingConfirmation,
1322 SwapStatus::Redeemable,
1323 SwapStatus::Redeemed,
1324 SwapStatus::Refundable,
1325 ]
1326 }
1327
1328 pub(crate) fn unexpired() -> Vec<SwapStatus> {
1329 vec![
1330 SwapStatus::Initial,
1331 SwapStatus::WaitingConfirmation,
1332 SwapStatus::Redeemable,
1333 SwapStatus::Redeemed,
1334 ]
1335 }
1336}
1337
1338impl TryFrom<i32> for SwapStatus {
1339 type Error = anyhow::Error;
1340
1341 fn try_from(value: i32) -> Result<Self, Self::Error> {
1342 match value {
1343 0 => Ok(SwapStatus::Initial),
1344 1 => Ok(SwapStatus::WaitingConfirmation),
1345 2 => Ok(SwapStatus::Redeemable),
1346 3 => Ok(SwapStatus::Redeemed),
1347 4 => Ok(SwapStatus::Refundable),
1348 5 => Ok(SwapStatus::Completed),
1349 _ => Err(anyhow!("illegal value")),
1350 }
1351 }
1352}
1353
1354#[derive(Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
1363pub struct SwapInfo {
1364 pub bitcoin_address: String,
1366 pub created_at: i64,
1368 pub lock_height: i64,
1370 pub payment_hash: Vec<u8>,
1372 pub preimage: Vec<u8>,
1374 pub private_key: Vec<u8>,
1376 pub public_key: Vec<u8>,
1378 pub swapper_public_key: Vec<u8>,
1380 pub script: Vec<u8>,
1382
1383 pub bolt11: Option<String>,
1385 pub paid_msat: u64,
1387 pub total_incoming_txs: u64,
1389 pub confirmed_sats: u64,
1391 pub unconfirmed_sats: u64,
1393 pub status: SwapStatus,
1395 pub refund_tx_ids: Vec<String>,
1397 pub unconfirmed_tx_ids: Vec<String>,
1399 pub confirmed_tx_ids: Vec<String>,
1401 pub min_allowed_deposit: i64,
1403 pub max_allowed_deposit: i64,
1405 pub max_swapper_payable: i64,
1407 pub last_redeem_error: Option<String>,
1409 pub channel_opening_fees: Option<OpeningFeeParams>,
1415 pub confirmed_at: Option<u32>,
1417}
1418
1419#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
1421pub struct UnspentTransactionOutput {
1422 pub txid: Vec<u8>,
1423 pub outnum: u32,
1424 pub amount_millisatoshi: u64,
1425 pub address: String,
1426 #[serde(default)]
1427 pub reserved: bool,
1428}
1429
1430#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1432#[serde(tag = "buy_bitcoin_provider")]
1433pub enum BuyBitcoinProvider {
1434 Moonpay,
1435}
1436
1437#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1441pub struct PrepareRedeemOnchainFundsRequest {
1442 pub to_address: String,
1443 pub sat_per_vbyte: u32,
1444}
1445
1446#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1449pub struct PrepareRedeemOnchainFundsResponse {
1450 pub tx_weight: u64,
1451 pub tx_fee_sat: u64,
1452}
1453
1454impl FromStr for BuyBitcoinProvider {
1455 type Err = anyhow::Error;
1456
1457 fn from_str(s: &str) -> Result<Self, Self::Err> {
1458 match s {
1459 "moonpay" => Ok(BuyBitcoinProvider::Moonpay),
1460 _ => Err(anyhow!("unknown buy bitcoin provider")),
1461 }
1462 }
1463}
1464
1465#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
1466pub struct PaymentPath {
1467 pub edges: Vec<PaymentPathEdge>,
1468}
1469
1470impl PaymentPath {
1471 pub fn final_hop_amount(&self, first_hop_amount_msat: u64) -> u64 {
1472 let mut max_to_send = first_hop_amount_msat;
1473 for h in self.edges.iter().skip(1) {
1474 max_to_send = h.amount_to_forward(max_to_send);
1475 }
1476 max_to_send
1477 }
1478
1479 pub fn first_hop_amount(&self, final_hop_amount_msat: u64) -> u64 {
1480 let mut first_hop_amount = final_hop_amount_msat;
1481 for h in self.edges.iter().skip(1).rev() {
1482 first_hop_amount = h.amount_from_forward(first_hop_amount);
1483 }
1484 first_hop_amount
1485 }
1486}
1487
1488#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
1489pub struct PaymentPathEdge {
1490 pub node_id: Vec<u8>,
1491 pub short_channel_id: String,
1492 pub channel_delay: u64,
1493 pub base_fee_msat: u64,
1494 pub fee_per_millionth: u64,
1495}
1496
1497impl PaymentPathEdge {
1498 pub(crate) fn amount_to_forward(&self, in_amount_msat: u64) -> u64 {
1499 let amount_to_forward = Self::divide_ceil(
1500 1_000_000 * (in_amount_msat - self.base_fee_msat),
1501 1_000_000 + self.fee_per_millionth,
1502 );
1503
1504 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);
1505 amount_to_forward
1506 }
1507
1508 pub(crate) fn amount_from_forward(&self, forward_amount_msat: u64) -> u64 {
1509 let in_amount_msat = self.base_fee_msat
1510 + forward_amount_msat * (1_000_000 + self.fee_per_millionth) / 1_000_000;
1511
1512 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);
1513 in_amount_msat
1514 }
1515
1516 fn divide_ceil(dividend: u64, factor: u64) -> u64 {
1517 dividend.div_ceil(factor)
1518 }
1519}
1520
1521pub(crate) mod sanitize {
1522 use crate::{FullReverseSwapInfo, SwapInfo};
1523
1524 pub(crate) trait Sanitize {
1525 fn sanitize(self) -> Self;
1527 }
1528
1529 pub(crate) fn sanitize_vec<T>(vals: Vec<T>) -> Vec<T>
1530 where
1531 T: Sanitize,
1532 {
1533 vals.into_iter()
1534 .map(|val| val.sanitize())
1535 .collect::<Vec<T>>()
1536 }
1537
1538 impl Sanitize for FullReverseSwapInfo {
1539 fn sanitize(self) -> FullReverseSwapInfo {
1540 FullReverseSwapInfo {
1541 preimage: vec![],
1542 private_key: vec![],
1543 ..self.clone()
1544 }
1545 }
1546 }
1547
1548 impl Sanitize for SwapInfo {
1549 fn sanitize(self) -> SwapInfo {
1550 SwapInfo {
1551 preimage: vec![],
1552 private_key: vec![],
1553 ..self.clone()
1554 }
1555 }
1556 }
1557}
1558
1559#[cfg(test)]
1560mod tests {
1561 use anyhow::Result;
1562 use prost::Message;
1563 use rand::random;
1564 use sdk_common::grpc;
1565
1566 use crate::models::sanitize::Sanitize;
1567 use crate::test_utils::{get_test_ofp, get_test_ofp_48h, rand_string, rand_vec_u8};
1568 use crate::{
1569 FullReverseSwapInfo, OpeningFeeParams, PaymentPath, PaymentPathEdge, ReverseSwapInfoCached,
1570 ReverseSwapStatus, SwapInfo,
1571 };
1572
1573 #[test]
1574 fn test_route_fees() -> Result<()> {
1575 let route = PaymentPath {
1576 edges: vec![
1577 PaymentPathEdge {
1578 node_id: vec![1],
1579 short_channel_id: "807189x2048x0".into(),
1580 channel_delay: 34,
1581 base_fee_msat: 1000,
1582 fee_per_millionth: 10,
1583 },
1584 PaymentPathEdge {
1585 node_id: vec![2],
1586 short_channel_id: "811871x2726x1".into(),
1587 channel_delay: 34,
1588 base_fee_msat: 0,
1589 fee_per_millionth: 0,
1590 },
1591 PaymentPathEdge {
1592 node_id: vec![3],
1593 short_channel_id: "16000000x0x18087".into(),
1594 channel_delay: 40,
1595 base_fee_msat: 1000,
1596 fee_per_millionth: 1,
1597 },
1598 ],
1599 };
1600 assert_eq!(route.final_hop_amount(1141000), 1139999);
1601 assert_eq!(route.first_hop_amount(1139999), 1141000);
1602
1603 let route = PaymentPath {
1604 edges: vec![
1605 PaymentPathEdge {
1606 node_id: vec![1],
1607 short_channel_id: "807189x2048x0".into(),
1608 channel_delay: 34,
1609 base_fee_msat: 1000,
1610 fee_per_millionth: 10,
1611 },
1612 PaymentPathEdge {
1613 node_id: vec![2],
1614 short_channel_id: "811871x2726x1".into(),
1615 channel_delay: 34,
1616 base_fee_msat: 0,
1617 fee_per_millionth: 0,
1618 },
1619 PaymentPathEdge {
1620 node_id: vec![3],
1621 short_channel_id: "16000000x0x18087".into(),
1622 channel_delay: 40,
1623 base_fee_msat: 0,
1624 fee_per_millionth: 2000,
1625 },
1626 ],
1627 };
1628 assert_eq!(route.final_hop_amount(1141314), 1139036);
1629 assert_eq!(route.first_hop_amount(1139036), 1141314);
1630
1631 Ok(())
1632 }
1633
1634 use super::OpeningFeeParamsMenu;
1635
1636 #[test]
1637 fn test_ofp_menu_validation() -> Result<()> {
1638 OpeningFeeParamsMenu::try_from(vec![get_test_ofp(10, 12, true)])?;
1640
1641 assert!(OpeningFeeParamsMenu::try_from(vec![
1643 get_test_ofp(10, 12, true),
1644 get_test_ofp(10, 12, true),
1645 ])
1646 .is_err());
1647
1648 OpeningFeeParamsMenu::try_from(vec![
1650 get_test_ofp(10, 12, true),
1651 get_test_ofp(12, 12, true),
1652 ])?;
1653
1654 OpeningFeeParamsMenu::try_from(vec![
1656 get_test_ofp(10, 12, true),
1657 get_test_ofp(10, 14, true),
1658 ])?;
1659
1660 OpeningFeeParamsMenu::try_from(vec![
1662 get_test_ofp(10, 12, true),
1663 get_test_ofp(12, 14, true),
1664 ])?;
1665
1666 assert!(OpeningFeeParamsMenu::try_from(vec![
1669 get_test_ofp(10, 12, true),
1670 get_test_ofp(10, 12, true),
1671 ])
1672 .is_err());
1673 assert!(OpeningFeeParamsMenu::try_from(vec![
1675 get_test_ofp(10, 12, true),
1676 get_test_ofp(10, 10, true),
1677 ])
1678 .is_err());
1679 assert!(OpeningFeeParamsMenu::try_from(vec![
1681 get_test_ofp(12, 10, true),
1682 get_test_ofp(10, 10, true),
1683 ])
1684 .is_err());
1685
1686 assert!(OpeningFeeParamsMenu::try_from(vec![
1688 get_test_ofp(10, 10, true),
1689 get_test_ofp(12, 12, false),
1690 ])
1691 .is_err());
1692
1693 assert!(OpeningFeeParamsMenu::try_from(vec![
1695 get_test_ofp(10, 10, false),
1696 get_test_ofp(12, 12, false),
1697 ])
1698 .is_err());
1699
1700 Ok(())
1701 }
1702
1703 #[test]
1704 fn test_payment_information_ser_de() -> Result<()> {
1705 let dummy_payment_info = grpc::PaymentInformation {
1706 payment_hash: rand_vec_u8(10),
1707 payment_secret: rand_vec_u8(10),
1708 destination: rand_vec_u8(10),
1709 incoming_amount_msat: random(),
1710 outgoing_amount_msat: random(),
1711 tag: "".to_string(),
1712 opening_fee_params: None,
1713 };
1714
1715 let mut buf = Vec::with_capacity(dummy_payment_info.encoded_len());
1716 dummy_payment_info.encode(&mut buf)?;
1717
1718 let decoded_payment_info = grpc::PaymentInformation::decode(&*buf)?;
1719
1720 assert_eq!(dummy_payment_info, decoded_payment_info);
1721
1722 Ok(())
1723 }
1724
1725 #[test]
1726 fn test_dynamic_fee_valid_until_format() -> Result<()> {
1727 let mut ofp: OpeningFeeParams = get_test_ofp(1, 1, true).into();
1728 ofp.valid_until = "2023-08-03T00:30:35.117Z".to_string();
1729 ofp.valid_until_date().map(|_| ())
1730 }
1731
1732 #[test]
1734 fn test_sanitization() -> Result<()> {
1735 let rev_swap_info_sanitized = FullReverseSwapInfo {
1736 id: "rev_swap_id".to_string(),
1737 created_at_block_height: 0,
1738 preimage: rand_vec_u8(32),
1739 private_key: vec![],
1740 claim_pubkey: "claim_pubkey".to_string(),
1741 timeout_block_height: 600_000,
1742 invoice: "645".to_string(),
1743 redeem_script: "redeem_script".to_string(),
1744 onchain_amount_sat: 250,
1745 sat_per_vbyte: Some(50),
1746 receive_amount_sat: None,
1747 cache: ReverseSwapInfoCached {
1748 status: ReverseSwapStatus::CompletedConfirmed,
1749 lockup_txid: Some("lockup_txid".to_string()),
1750 claim_txid: Some("claim_txid".to_string()),
1751 },
1752 }
1753 .sanitize();
1754 assert_eq!(rev_swap_info_sanitized.preimage, Vec::<u8>::new());
1755 assert_eq!(rev_swap_info_sanitized.private_key, Vec::<u8>::new());
1756
1757 let swap_info_sanitized = SwapInfo {
1758 bitcoin_address: rand_string(10),
1759 created_at: 10,
1760 lock_height: random(),
1761 payment_hash: rand_vec_u8(32),
1762 preimage: rand_vec_u8(32),
1763 private_key: rand_vec_u8(32),
1764 public_key: rand_vec_u8(10),
1765 swapper_public_key: rand_vec_u8(10),
1766 script: rand_vec_u8(10),
1767 bolt11: None,
1768 paid_msat: 0,
1769 unconfirmed_sats: 0,
1770 confirmed_sats: 0,
1771 total_incoming_txs: 0,
1772 status: crate::models::SwapStatus::Initial,
1773 refund_tx_ids: Vec::new(),
1774 unconfirmed_tx_ids: Vec::new(),
1775 confirmed_tx_ids: Vec::new(),
1776 min_allowed_deposit: 0,
1777 max_allowed_deposit: 100,
1778 max_swapper_payable: 200,
1779 last_redeem_error: None,
1780 channel_opening_fees: Some(get_test_ofp_48h(random(), random()).into()),
1781 confirmed_at: None,
1782 }
1783 .sanitize();
1784 assert_eq!(swap_info_sanitized.preimage, Vec::<u8>::new());
1785 assert_eq!(swap_info_sanitized.private_key, Vec::<u8>::new());
1786
1787 Ok(())
1788 }
1789}