1use std::cmp::max;
2use std::ops::Add;
3use std::str::FromStr;
4
5use anyhow::{anyhow, ensure, Result};
6use chrono::{DateTime, Duration, Utc};
7use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
8use rusqlite::ToSql;
9use sdk_common::grpc;
10use sdk_common::prelude::Network::*;
11use sdk_common::prelude::*;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use strum_macros::{Display, EnumString};
15
16use crate::bitcoin::{
17 absolute::LockTime,
18 blockdata::{opcodes, script::Builder},
19 hashes::{sha256, Hash},
20 script::PushBytes,
21 secp256k1::{PublicKey, Secp256k1, SecretKey},
22 Address, ScriptBuf,
23};
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: sha256::Hash,
178 compressed_pub_key: [u8; 33],
179 refund_address: Vec<u8>,
180 lock_height: u32,
181 ) -> ReverseSwapResult<ScriptBuf> {
182 let ripemd160_hash = bitcoin::hashes::ripemd160::Hash::hash(preimage_hash.as_byte_array());
183 let lock_height = LockTime::from_height(lock_height)?;
184 let refund_address: &PushBytes = refund_address.as_slice().try_into()?;
185
186 Ok(Builder::new()
187 .push_opcode(opcodes::all::OP_SIZE)
188 .push_slice([0x20])
189 .push_opcode(opcodes::all::OP_EQUAL)
190 .push_opcode(opcodes::all::OP_IF)
191 .push_opcode(opcodes::all::OP_HASH160)
192 .push_slice(ripemd160_hash.as_byte_array())
193 .push_opcode(opcodes::all::OP_EQUALVERIFY)
194 .push_slice(compressed_pub_key)
195 .push_opcode(opcodes::all::OP_ELSE)
196 .push_opcode(opcodes::all::OP_DROP)
197 .push_lock_time(lock_height)
198 .push_opcode(opcodes::all::OP_CLTV)
199 .push_opcode(opcodes::all::OP_DROP)
200 .push_slice(refund_address)
201 .push_opcode(opcodes::all::OP_ENDIF)
202 .push_opcode(opcodes::all::OP_CHECKSIG)
203 .into_script())
204 }
205
206 pub(crate) fn validate_redeem_script(
213 &self,
214 received_lockup_address: String,
215 network: Network,
216 ) -> ReverseSwapResult<()> {
217 let redeem_script_received = ScriptBuf::from_hex(&self.redeem_script)?;
218 let asm = redeem_script_received.as_script().to_asm_string();
219 debug!("received asm = {asm:?}");
220
221 let sk = SecretKey::from_slice(&self.private_key)?;
222 let pk = PublicKey::from_secret_key(&Secp256k1::new(), &sk);
223
224 let asm_elements: Vec<&str> = asm.split(' ').collect();
226 let refund_address = asm_elements.get(18).unwrap_or(&"").to_string();
227 let refund_address_bytes = hex::decode(refund_address)?;
228
229 let redeem_script_expected = Self::build_expected_reverse_swap_script(
230 self.get_preimage_hash(), pk.serialize(), refund_address_bytes,
233 self.timeout_block_height,
234 )?;
235 debug!(
236 "expected asm = {:?}",
237 redeem_script_expected.as_script().to_asm_string()
238 );
239
240 match redeem_script_received.eq(&redeem_script_expected) {
241 true => {
242 let lockup_addr_expected = &received_lockup_address;
243 let lockup_addr_from_script =
244 &Address::p2wsh(&redeem_script_received, network.into()).to_string();
245
246 match lockup_addr_from_script == lockup_addr_expected {
247 true => Ok(()),
248 false => Err(ReverseSwapError::UnexpectedLockupAddress),
249 }
250 }
251 false => Err(ReverseSwapError::UnexpectedRedeemScript),
252 }
253 }
254
255 pub(crate) fn validate_invoice_amount(
256 &self,
257 expected_amount_msat: u64,
258 ) -> ReverseSwapResult<()> {
259 let inv: crate::lightning_invoice::Bolt11Invoice = self.invoice.parse()?;
260
261 let amount_from_invoice_msat = inv.amount_milli_satoshis().unwrap_or_default();
263 match amount_from_invoice_msat == expected_amount_msat {
264 false => Err(ReverseSwapError::unexpected_invoice_amount(
265 "Does not match the request",
266 )),
267 true => Ok(()),
268 }
269 }
270
271 pub(crate) fn validate_invoice(&self, expected_amount_msat: u64) -> ReverseSwapResult<()> {
277 self.validate_invoice_amount(expected_amount_msat)?;
278
279 let inv: crate::lightning_invoice::Bolt11Invoice = self.invoice.parse()?;
281 let preimage_hash_from_invoice = inv.payment_hash();
282 let preimage_hash_from_req = &self.get_preimage_hash();
283 match preimage_hash_from_invoice == preimage_hash_from_req {
284 false => Err(ReverseSwapError::unexpected_payment_hash(
285 "Does not match the request",
286 )),
287 true => Ok(()),
288 }
289 }
290
291 pub(crate) fn get_lockup_address(&self, network: Network) -> ReverseSwapResult<Address> {
293 let redeem_script = ScriptBuf::from_hex(&self.redeem_script)?;
294 Ok(Address::p2wsh(redeem_script.as_script(), network.into()))
295 }
296
297 pub(crate) fn get_preimage_hash(&self) -> sha256::Hash {
299 sha256::Hash::hash(&self.preimage)
300 }
301
302 pub(crate) fn get_reverse_swap_info_using_cached_values(&self) -> ReverseSwapInfo {
304 ReverseSwapInfo {
305 id: self.id.clone(),
306 claim_pubkey: self.claim_pubkey.clone(),
307 lockup_txid: self.cache.clone().lockup_txid,
308 claim_txid: self.cache.claim_txid.clone(),
309 onchain_amount_sat: self.onchain_amount_sat,
310 status: self.cache.status,
311 }
312 }
313}
314
315#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
317pub struct ReverseSwapInfo {
318 pub id: String,
319 pub claim_pubkey: String,
320 pub lockup_txid: Option<String>,
322 pub claim_txid: Option<String>,
324 pub onchain_amount_sat: u64,
325 pub status: ReverseSwapStatus,
326}
327
328#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
332pub enum ReverseSwapStatus {
333 Initial = 0,
337
338 InProgress = 1,
341
342 Cancelled = 2,
345
346 CompletedSeen = 3,
348
349 CompletedConfirmed = 4,
351}
352
353impl ReverseSwapStatus {
354 pub(crate) fn is_monitored_state(&self) -> bool {
355 matches!(
356 self,
357 ReverseSwapStatus::Initial
358 | ReverseSwapStatus::InProgress
359 | ReverseSwapStatus::CompletedSeen
360 )
361 }
362
363 pub(crate) fn is_blocking_state(&self) -> bool {
364 matches!(
365 self,
366 ReverseSwapStatus::Initial | ReverseSwapStatus::InProgress
367 )
368 }
369}
370
371impl TryFrom<i32> for ReverseSwapStatus {
372 type Error = anyhow::Error;
373
374 fn try_from(value: i32) -> Result<Self, Self::Error> {
375 match value {
376 0 => Ok(ReverseSwapStatus::Initial),
377 1 => Ok(ReverseSwapStatus::InProgress),
378 2 => Ok(ReverseSwapStatus::Cancelled),
379 3 => Ok(ReverseSwapStatus::CompletedSeen),
380 4 => Ok(ReverseSwapStatus::CompletedConfirmed),
381 _ => Err(anyhow!("illegal value")),
382 }
383 }
384}
385
386#[tonic::async_trait]
388pub(crate) trait ReverseSwapperRoutingAPI: Send + Sync {
389 async fn fetch_reverse_routing_node(&self) -> ReverseSwapResult<Vec<u8>>;
390}
391
392#[tonic::async_trait]
393impl ReverseSwapperRoutingAPI for BreezServer {
394 async fn fetch_reverse_routing_node(&self) -> ReverseSwapResult<Vec<u8>> {
395 let mut client = self.get_swapper_client().await;
396
397 Ok(with_connection_retry!(
398 client.get_reverse_routing_node(grpc::GetReverseRoutingNodeRequest::default())
399 )
400 .await
401 .map(|reply| reply.into_inner().node_id)?)
402 }
403}
404
405#[tonic::async_trait]
407pub(crate) trait ReverseSwapServiceAPI: Send + Sync {
408 async fn fetch_reverse_swap_fees(&self) -> ReverseSwapResult<ReverseSwapPairInfo>;
411
412 async fn create_reverse_swap_on_remote(
422 &self,
423 send_amount_sat: u64,
424 preimage_hash_hex: String,
425 claim_pubkey: String,
426 pair_hash: String,
427 routing_node: String,
428 ) -> ReverseSwapResult<BoltzApiCreateReverseSwapResponse>;
429
430 async fn get_boltz_status(&self, id: String) -> ReverseSwapResult<BoltzApiReverseSwapStatus>;
432
433 async fn get_route_hints(&self, routing_node_id: String) -> ReverseSwapResult<Vec<RouteHint>>;
435}
436
437#[derive(Clone, Debug)]
439pub struct LogEntry {
440 pub line: String,
441 pub level: String,
442}
443
444#[derive(Clone)]
449pub struct Config {
450 pub breezserver: String,
451 pub chainnotifier_url: String,
452 pub mempoolspace_url: Option<String>,
459 pub working_dir: String,
462 pub network: Network,
463 pub payment_timeout_sec: u32,
464 pub default_lsp_id: Option<String>,
465 pub api_key: Option<String>,
466 pub maxfee_percent: f64,
468 pub exemptfee_msat: u64,
470 pub node_config: NodeConfig,
471}
472
473impl Config {
474 pub fn production(api_key: String, node_config: NodeConfig) -> Self {
475 Config {
476 breezserver: PRODUCTION_BREEZSERVER_URL.to_string(),
477 chainnotifier_url: "https://chainnotifier.breez.technology".to_string(),
478 mempoolspace_url: None,
479 working_dir: ".".to_string(),
480 network: Bitcoin,
481 payment_timeout_sec: 60,
482 default_lsp_id: None,
483 api_key: Some(api_key),
484 maxfee_percent: 1.0,
485 exemptfee_msat: 20000,
486 node_config,
487 }
488 }
489
490 pub fn staging(api_key: String, node_config: NodeConfig) -> Self {
491 Config {
492 breezserver: STAGING_BREEZSERVER_URL.to_string(),
493 chainnotifier_url: "https://chainnotifier.breez.technology".to_string(),
494 mempoolspace_url: None,
495 working_dir: ".".to_string(),
496 network: Bitcoin,
497 payment_timeout_sec: 60,
498 default_lsp_id: None,
499 api_key: Some(api_key),
500 maxfee_percent: 0.5,
501 exemptfee_msat: 20000,
502 node_config,
503 }
504 }
505
506 pub fn regtest(api_key: String, node_config: NodeConfig) -> Self {
507 Config {
508 breezserver: REGTEST_BREEZSERVER_URL.to_string(),
509 chainnotifier_url: "https://chainnotifier.breez.technology".to_string(),
510 mempoolspace_url: Some(REGTEST_MEMPOOL_URL.to_string()),
511 working_dir: ".".to_string(),
512 network: Regtest,
513 payment_timeout_sec: 60,
514 default_lsp_id: None,
515 api_key: Some(api_key),
516 maxfee_percent: 0.5,
517 exemptfee_msat: 20000,
518 node_config,
519 }
520 }
521}
522
523#[derive(Clone)]
524pub enum NodeConfig {
525 Greenlight { config: GreenlightNodeConfig },
526}
527
528#[derive(Clone, Serialize)]
529pub enum NodeCredentials {
530 Greenlight {
531 credentials: GreenlightDeviceCredentials,
532 },
533}
534
535#[derive(Clone)]
536pub struct GreenlightNodeConfig {
537 pub partner_credentials: Option<GreenlightCredentials>,
538 pub invite_code: Option<String>,
539}
540
541#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, EnumString)]
543pub enum EnvironmentType {
544 #[strum(serialize = "production")]
545 Production,
546 #[strum(serialize = "staging")]
547 Staging,
548 #[strum(serialize = "regtest")]
549 Regtest,
550}
551
552#[derive(Clone, Serialize, Deserialize)]
554pub struct GreenlightCredentials {
555 pub developer_key: Vec<u8>,
556 pub developer_cert: Vec<u8>,
557}
558
559#[derive(Clone, Serialize, Deserialize)]
561pub struct GreenlightDeviceCredentials {
562 pub device: Vec<u8>,
563}
564
565#[derive(Default)]
567pub struct ConfigureNodeRequest {
568 pub close_to_address: Option<String>,
569}
570
571pub struct ConnectRequest {
573 pub config: Config,
574 pub seed: Vec<u8>,
575 pub restore_only: Option<bool>,
577}
578
579#[derive(PartialEq)]
581pub enum PaymentTypeFilter {
582 Sent,
583 Received,
584 ClosedChannel,
585}
586
587pub struct MetadataFilter {
589 pub json_path: String,
591 pub json_value: String,
594}
595
596pub enum FeeratePreset {
598 Regular,
599 Economy,
600 Priority,
601}
602
603impl TryFrom<i32> for FeeratePreset {
604 type Error = anyhow::Error;
605
606 fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
607 match value {
608 0 => Ok(FeeratePreset::Regular),
609 1 => Ok(FeeratePreset::Economy),
610 2 => Ok(FeeratePreset::Priority),
611 _ => Err(anyhow!("Unexpected feerate enum value")),
612 }
613 }
614}
615
616#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
617pub struct BackupStatus {
618 pub backed_up: bool,
619 pub last_backup_time: Option<u64>,
621}
622
623#[derive(Serialize, Default, Deserialize, Clone, PartialEq, Eq, Debug)]
629pub struct NodeState {
630 pub id: String,
631 pub block_height: u32,
632 pub channels_balance_msat: u64,
633 pub onchain_balance_msat: u64,
634 #[serde(default)]
635 pub pending_onchain_balance_msat: u64,
636
637 #[serde(default)]
638 pub utxos: Vec<UnspentTransactionOutput>,
639 pub max_payable_msat: u64,
640 pub max_receivable_msat: u64,
641 pub max_single_payment_amount_msat: u64,
642 pub max_chan_reserve_msats: u64,
643 pub connected_peers: Vec<String>,
644
645 pub max_receivable_single_payment_amount_msat: u64,
647
648 pub total_inbound_liquidity_msats: u64,
650}
651
652pub struct SyncResponse {
654 pub sync_state: Value,
655 pub node_state: NodeState,
656 pub payments: Vec<crate::models::Payment>,
657 pub channels: Vec<crate::models::Channel>,
658}
659
660#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
662pub enum PaymentStatus {
663 #[default]
664 Pending = 0,
665 Complete = 1,
666 Failed = 2,
667}
668
669#[derive(Default, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
671pub struct Payment {
672 pub id: String,
673 pub payment_type: PaymentType,
674 pub payment_time: i64,
676 pub amount_msat: u64,
677 pub fee_msat: u64,
678 pub status: PaymentStatus,
679 pub error: Option<String>,
680 pub description: Option<String>,
681 pub details: PaymentDetails,
682 pub metadata: Option<String>,
683}
684
685#[derive(Default)]
687pub struct PaymentExternalInfo {
688 pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
689 pub lnurl_pay_domain: Option<String>,
690 pub lnurl_pay_comment: Option<String>,
691 pub lnurl_metadata: Option<String>,
692 pub ln_address: Option<String>,
693 pub lnurl_withdraw_endpoint: Option<String>,
694 pub attempted_amount_msat: Option<u64>,
695 pub attempted_error: Option<String>,
696}
697
698#[derive(Default)]
700pub struct ListPaymentsRequest {
701 pub filters: Option<Vec<PaymentTypeFilter>>,
702 pub metadata_filters: Option<Vec<MetadataFilter>>,
703 pub from_timestamp: Option<i64>,
705 pub to_timestamp: Option<i64>,
707 pub include_failures: Option<bool>,
708 pub offset: Option<u32>,
709 pub limit: Option<u32>,
710}
711
712#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
714pub struct PaymentResponse {
715 pub payment_time: i64,
717 pub amount_msat: u64,
718 pub fee_msat: u64,
719 pub payment_hash: String,
720 pub payment_preimage: String,
721}
722
723#[allow(clippy::large_enum_variant)]
725#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
726#[serde(untagged)]
727pub enum PaymentDetails {
728 Ln {
729 #[serde(flatten)]
730 data: LnPaymentDetails,
731 },
732 ClosedChannel {
733 #[serde(flatten)]
734 data: ClosedChannelPaymentDetails,
735 },
736}
737
738impl Default for PaymentDetails {
739 fn default() -> Self {
740 Self::Ln {
741 data: LnPaymentDetails::default(),
742 }
743 }
744}
745
746impl PaymentDetails {
747 pub fn add_pending_expiration_block(&mut self, htlc: Htlc) {
748 if let PaymentDetails::Ln { data } = self {
749 data.pending_expiration_block = Some(htlc.expiry)
750 }
751 }
752}
753
754#[derive(Default, PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
756pub struct LnPaymentDetails {
757 pub payment_hash: String,
758 pub label: String,
759 pub destination_pubkey: String,
760 pub payment_preimage: String,
761 pub keysend: bool,
762 pub bolt11: String,
763
764 pub open_channel_bolt11: Option<String>,
767
768 pub lnurl_success_action: Option<SuccessActionProcessed>,
771
772 pub lnurl_pay_domain: Option<String>,
774
775 pub lnurl_pay_comment: Option<String>,
777
778 pub ln_address: Option<String>,
780
781 pub lnurl_metadata: Option<String>,
783
784 pub lnurl_withdraw_endpoint: Option<String>,
786
787 pub swap_info: Option<SwapInfo>,
789
790 pub reverse_swap_info: Option<ReverseSwapInfo>,
792
793 pub pending_expiration_block: Option<u32>,
795}
796
797#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
799pub struct ClosedChannelPaymentDetails {
800 pub state: ChannelState,
801 pub funding_txid: String,
802 pub short_channel_id: Option<String>,
803 pub closing_txid: Option<String>,
805}
806
807#[derive(Clone, Debug, Default, Serialize, Deserialize)]
808pub struct ReverseSwapFeesRequest {
809 pub send_amount_sat: Option<u64>,
811 pub claim_tx_feerate: Option<u32>,
813}
814
815#[derive(Clone, Debug, Serialize, Deserialize)]
816pub struct MaxChannelAmount {
817 pub channel_id: String,
819 pub amount_msat: u64,
821 pub path: PaymentPath,
823}
824
825#[derive(Clone, Debug, Default, Serialize, Deserialize)]
827pub struct ReceivePaymentRequest {
828 pub amount_msat: u64,
830 pub description: String,
832 pub preimage: Option<Vec<u8>>,
835 pub opening_fee_params: Option<OpeningFeeParams>,
838 pub use_description_hash: Option<bool>,
840 pub expiry: Option<u32>,
842 pub cltv: Option<u32>,
844}
845
846#[derive(Clone, Debug, Serialize, Deserialize)]
852pub struct ReceivePaymentResponse {
853 pub ln_invoice: LNInvoice,
855 pub opening_fee_params: Option<OpeningFeeParams>,
857 pub opening_fee_msat: Option<u64>,
859}
860
861#[derive(Clone, Debug, Serialize, Deserialize)]
863pub struct SendPaymentRequest {
864 pub bolt11: String,
866 pub use_trampoline: bool,
870 pub amount_msat: Option<u64>,
872 pub label: Option<String>,
874}
875
876#[derive(Clone, Debug, Serialize, Deserialize)]
878pub struct TlvEntry {
879 pub field_number: u64,
881 pub value: Vec<u8>,
883}
884
885#[derive(Clone, Debug, Serialize, Deserialize)]
887pub struct SendSpontaneousPaymentRequest {
888 pub node_id: String,
890 pub amount_msat: u64,
892 pub extra_tlvs: Option<Vec<TlvEntry>>,
894 pub label: Option<String>,
896}
897
898#[derive(Clone, Debug, Serialize, Deserialize)]
900pub struct SendPaymentResponse {
901 pub payment: Payment,
902}
903
904#[derive(Clone, Debug, Serialize, Deserialize)]
905pub struct ReportPaymentFailureDetails {
906 pub payment_hash: String,
908 pub comment: Option<String>,
910}
911
912#[derive(Clone, Debug, Serialize, Deserialize)]
914pub enum ReportIssueRequest {
915 PaymentFailure { data: ReportPaymentFailureDetails },
916}
917
918#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
920pub enum HealthCheckStatus {
921 Operational,
922 Maintenance,
923 ServiceDisruption,
924}
925
926#[derive(Clone, Debug, Serialize, Deserialize)]
928pub struct ServiceHealthCheckResponse {
929 pub status: HealthCheckStatus,
930}
931
932#[tonic::async_trait]
934pub trait SupportAPI: Send + Sync {
935 async fn service_health_check(&self) -> SdkResult<ServiceHealthCheckResponse>;
936
937 async fn report_payment_failure(
938 &self,
939 node_state: NodeState,
940 payment: Payment,
941 lsp_id: Option<String>,
942 comment: Option<String>,
943 ) -> SdkResult<()>;
944}
945
946#[derive(Clone)]
947pub struct StaticBackupRequest {
948 pub working_dir: String,
949}
950
951#[derive(Clone, Debug, Serialize, Deserialize)]
952pub struct StaticBackupResponse {
953 pub backup: Option<Vec<String>>,
954}
955
956#[derive(Default)]
957pub struct OpenChannelFeeRequest {
958 pub amount_msat: Option<u64>,
959 pub expiry: Option<u32>,
960}
961
962#[derive(Clone, Debug, Serialize, Deserialize)]
963pub struct OpenChannelFeeResponse {
964 pub fee_msat: Option<u64>,
967 pub fee_params: OpeningFeeParams,
969}
970
971#[derive(Clone, Debug, Default, Serialize, Deserialize)]
972pub struct ReceiveOnchainRequest {
973 pub opening_fee_params: Option<OpeningFeeParams>,
974}
975
976#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
977pub struct ListSwapsRequest {
978 pub status: Option<Vec<SwapStatus>>,
979 pub from_timestamp: Option<i64>,
981 pub to_timestamp: Option<i64>,
983 pub offset: Option<u32>,
984 pub limit: Option<u32>,
985}
986
987#[derive(Clone, Debug, Serialize, Deserialize)]
988pub struct BuyBitcoinRequest {
989 pub provider: BuyBitcoinProvider,
990 pub opening_fee_params: Option<OpeningFeeParams>,
991 pub redirect_url: Option<String>,
995}
996
997#[derive(Clone, Debug, Serialize, Deserialize)]
998pub struct BuyBitcoinResponse {
999 pub url: String,
1000 pub opening_fee_params: Option<OpeningFeeParams>,
1001}
1002
1003#[derive(Clone, Debug, Serialize, Deserialize)]
1004pub struct RedeemOnchainFundsRequest {
1005 pub to_address: String,
1006 pub sat_per_vbyte: u32,
1007}
1008
1009#[derive(Clone, Debug, Serialize, Deserialize)]
1010pub struct RedeemOnchainFundsResponse {
1011 pub txid: Vec<u8>,
1012}
1013
1014pub enum SwapAmountType {
1015 Send,
1016 Receive,
1017}
1018
1019pub struct PrepareOnchainPaymentRequest {
1021 pub amount_sat: u64,
1023 pub amount_type: SwapAmountType,
1024
1025 pub claim_tx_feerate: u32,
1027}
1028
1029#[derive(Serialize)]
1030pub struct OnchainPaymentLimitsResponse {
1031 pub min_sat: u64,
1033 pub max_sat: u64,
1035 pub max_payable_sat: u64,
1037}
1038
1039#[derive(Clone, Debug, Serialize)]
1042pub struct PrepareOnchainPaymentResponse {
1043 pub fees_hash: String,
1044 pub fees_percentage: f64,
1045 pub fees_lockup: u64,
1046 pub fees_claim: u64,
1047
1048 pub sender_amount_sat: u64,
1049 pub recipient_amount_sat: u64,
1050 pub total_fees: u64,
1051}
1052
1053#[derive(Clone, Debug)]
1054pub struct PayOnchainRequest {
1055 pub recipient_address: String,
1056 pub prepare_res: PrepareOnchainPaymentResponse,
1057}
1058
1059#[derive(Serialize)]
1060pub struct PayOnchainResponse {
1061 pub reverse_swap_info: ReverseSwapInfo,
1062}
1063
1064pub struct PrepareRefundRequest {
1065 pub swap_address: String,
1066 pub to_address: String,
1067 pub sat_per_vbyte: u32,
1068 pub unilateral: Option<bool>,
1069}
1070
1071pub struct RefundRequest {
1072 pub swap_address: String,
1073 pub to_address: String,
1074 pub sat_per_vbyte: u32,
1075 pub unilateral: Option<bool>,
1076}
1077
1078pub struct PrepareRefundResponse {
1079 pub refund_tx_weight: u32,
1080 pub refund_tx_fee_sat: u64,
1081}
1082
1083pub struct RefundResponse {
1084 pub refund_tx_id: String,
1085}
1086
1087#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1092pub struct OpeningFeeParams {
1093 pub min_msat: u64,
1095 pub proportional: u32,
1097 pub valid_until: String,
1099 pub max_idle_time: u32,
1101 pub max_client_to_self_delay: u32,
1102 pub promise: String,
1103}
1104
1105impl OpeningFeeParams {
1106 pub(crate) fn valid_until_date(&self) -> Result<DateTime<Utc>> {
1107 Ok(DateTime::parse_from_rfc3339(&self.valid_until).map(|d| d.with_timezone(&Utc))?)
1108 }
1109
1110 pub(crate) fn valid_for(&self, expiry: u32) -> Result<bool> {
1111 Ok(self.valid_until_date()? > Utc::now().add(Duration::seconds(expiry as i64)))
1112 }
1113
1114 pub(crate) fn get_channel_fees_msat_for(&self, amount_msats: u64) -> u64 {
1115 let lsp_fee_msat = amount_msats * self.proportional as u64 / 1_000_000;
1116 let lsp_fee_msat_rounded_to_sat = lsp_fee_msat / 1000 * 1000;
1117
1118 max(lsp_fee_msat_rounded_to_sat, self.min_msat)
1119 }
1120}
1121
1122impl From<OpeningFeeParams> for grpc::OpeningFeeParams {
1123 fn from(ofp: OpeningFeeParams) -> Self {
1124 Self {
1125 min_msat: ofp.min_msat,
1126 proportional: ofp.proportional,
1127 valid_until: ofp.valid_until,
1128 max_idle_time: ofp.max_idle_time,
1129 max_client_to_self_delay: ofp.max_client_to_self_delay,
1130 promise: ofp.promise,
1131 }
1132 }
1133}
1134
1135impl From<grpc::OpeningFeeParams> for OpeningFeeParams {
1136 fn from(gofp: grpc::OpeningFeeParams) -> Self {
1137 Self {
1138 min_msat: gofp.min_msat,
1139 proportional: gofp.proportional,
1140 valid_until: gofp.valid_until,
1141 max_idle_time: gofp.max_idle_time,
1142 max_client_to_self_delay: gofp.max_client_to_self_delay,
1143 promise: gofp.promise,
1144 }
1145 }
1146}
1147
1148impl FromSql for OpeningFeeParams {
1149 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1150 serde_json::from_str(value.as_str()?).map_err(|_| FromSqlError::InvalidType)
1151 }
1152}
1153
1154impl ToSql for OpeningFeeParams {
1155 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1156 Ok(ToSqlOutput::from(
1157 serde_json::to_string(&self).map_err(|_| FromSqlError::InvalidType)?,
1158 ))
1159 }
1160}
1161
1162pub enum DynamicFeeType {
1163 Cheapest,
1164 Longest,
1165}
1166
1167#[derive(Debug, Clone, Serialize, Deserialize)]
1169pub struct OpeningFeeParamsMenu {
1170 pub values: Vec<OpeningFeeParams>,
1171}
1172
1173impl OpeningFeeParamsMenu {
1174 pub fn try_from(values: Vec<sdk_common::grpc::OpeningFeeParams>) -> Result<Self> {
1180 let temp = Self {
1181 values: values
1182 .into_iter()
1183 .map(|ofp| ofp.into())
1184 .collect::<Vec<OpeningFeeParams>>(),
1185 };
1186 temp.validate().map(|_| temp)
1187 }
1188
1189 fn validate(&self) -> Result<()> {
1190 let is_ordered = self.values.windows(2).all(|ofp| {
1192 let larger_min_msat_fee = ofp[0].min_msat < ofp[1].min_msat;
1193 let equal_min_msat_fee = ofp[0].min_msat == ofp[1].min_msat;
1194
1195 let larger_proportional = ofp[0].proportional < ofp[1].proportional;
1196 let equal_proportional = ofp[0].proportional == ofp[1].proportional;
1197
1198 (larger_min_msat_fee && equal_proportional)
1199 || (equal_min_msat_fee && larger_proportional)
1200 || (larger_min_msat_fee && larger_proportional)
1201 });
1202 ensure!(is_ordered, "Validation failed: fee params are not ordered");
1203
1204 let is_expired = self.values.iter().any(|ofp| match ofp.valid_until_date() {
1206 Ok(valid_until) => Utc::now() > valid_until,
1207 Err(_) => {
1208 warn!("Failed to parse valid_until for OpeningFeeParams: {ofp:?}");
1209 false
1210 }
1211 });
1212 ensure!(!is_expired, "Validation failed: expired fee params found");
1213
1214 Ok(())
1215 }
1216
1217 pub fn get_cheapest_opening_fee_params(&self) -> Result<OpeningFeeParams> {
1218 self.values.first().cloned().ok_or_else(|| {
1219 anyhow!("The LSP doesn't support opening new channels: Dynamic fees menu contains no values")
1220 })
1221 }
1222
1223 pub fn get_48h_opening_fee_params(&self) -> Result<OpeningFeeParams> {
1224 let now = Utc::now();
1226 let duration_48h = chrono::Duration::hours(48);
1227 let valid_min_48h: Vec<OpeningFeeParams> = self
1228 .values
1229 .iter()
1230 .filter(|ofp| match ofp.valid_until_date() {
1231 Ok(valid_until) => valid_until - now > duration_48h,
1232 Err(_) => {
1233 warn!("Failed to parse valid_until for OpeningFeeParams: {ofp:?}");
1234 false
1235 }
1236 })
1237 .cloned()
1238 .collect();
1239
1240 valid_min_48h.first().cloned().ok_or_else(|| {
1243 anyhow!("The LSP doesn't support opening fees that are valid for at least 48 hours")
1244 })
1245 }
1246}
1247
1248#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
1250pub struct Channel {
1251 pub funding_txid: String,
1252 pub short_channel_id: Option<String>,
1253 pub state: ChannelState,
1254 pub spendable_msat: u64,
1255 pub local_balance_msat: u64,
1256 pub receivable_msat: u64,
1257 pub closed_at: Option<u64>,
1258 pub funding_outnum: Option<u32>,
1260 pub alias_local: Option<String>,
1261 pub alias_remote: Option<String>,
1262 pub closing_txid: Option<String>,
1266
1267 pub htlcs: Vec<Htlc>,
1268}
1269
1270#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
1271pub struct Htlc {
1272 pub expiry: u32,
1273 pub payment_hash: Vec<u8>,
1274}
1275
1276impl Htlc {
1277 pub fn from(expiry: u32, payment_hash: Vec<u8>) -> Self {
1278 Htlc {
1279 expiry,
1280 payment_hash,
1281 }
1282 }
1283}
1284
1285#[derive(Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize)]
1287pub enum ChannelState {
1288 PendingOpen,
1289 Opened,
1290 PendingClose,
1291 Closed,
1292}
1293
1294#[derive(Copy, Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
1296pub enum SwapStatus {
1297 #[default]
1301 Initial = 0,
1302
1303 WaitingConfirmation = 1,
1304
1305 Redeemable = 2,
1306
1307 Redeemed = 3,
1308
1309 Refundable = 4,
1313
1314 Completed = 5,
1315}
1316
1317impl SwapStatus {
1318 pub(crate) fn unused() -> Vec<SwapStatus> {
1319 vec![SwapStatus::Initial]
1320 }
1321
1322 pub(crate) fn in_progress() -> Vec<SwapStatus> {
1323 vec![SwapStatus::Redeemable, SwapStatus::WaitingConfirmation]
1324 }
1325
1326 pub(crate) fn redeemable() -> Vec<SwapStatus> {
1327 vec![SwapStatus::Redeemable]
1328 }
1329
1330 pub(crate) fn refundable() -> Vec<SwapStatus> {
1331 vec![SwapStatus::Refundable]
1332 }
1333
1334 pub(crate) fn monitored() -> Vec<SwapStatus> {
1335 vec![
1336 SwapStatus::Initial,
1337 SwapStatus::WaitingConfirmation,
1338 SwapStatus::Redeemable,
1339 SwapStatus::Redeemed,
1340 SwapStatus::Refundable,
1341 ]
1342 }
1343
1344 pub(crate) fn unexpired() -> Vec<SwapStatus> {
1345 vec![
1346 SwapStatus::Initial,
1347 SwapStatus::WaitingConfirmation,
1348 SwapStatus::Redeemable,
1349 SwapStatus::Redeemed,
1350 ]
1351 }
1352}
1353
1354impl TryFrom<i32> for SwapStatus {
1355 type Error = anyhow::Error;
1356
1357 fn try_from(value: i32) -> Result<Self, Self::Error> {
1358 match value {
1359 0 => Ok(SwapStatus::Initial),
1360 1 => Ok(SwapStatus::WaitingConfirmation),
1361 2 => Ok(SwapStatus::Redeemable),
1362 3 => Ok(SwapStatus::Redeemed),
1363 4 => Ok(SwapStatus::Refundable),
1364 5 => Ok(SwapStatus::Completed),
1365 _ => Err(anyhow!("illegal value")),
1366 }
1367 }
1368}
1369
1370#[derive(Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
1379pub struct SwapInfo {
1380 pub bitcoin_address: String,
1382 pub created_at: i64,
1384 pub lock_height: i64,
1386 pub payment_hash: Vec<u8>,
1388 pub preimage: Vec<u8>,
1390 pub private_key: Vec<u8>,
1392 pub public_key: Vec<u8>,
1394 pub swapper_public_key: Vec<u8>,
1396 pub script: Vec<u8>,
1398
1399 pub bolt11: Option<String>,
1401 pub paid_msat: u64,
1403 pub total_incoming_txs: u64,
1405 pub confirmed_sats: u64,
1407 pub unconfirmed_sats: u64,
1409 pub status: SwapStatus,
1411 pub refund_tx_ids: Vec<String>,
1413 pub unconfirmed_tx_ids: Vec<String>,
1415 pub confirmed_tx_ids: Vec<String>,
1417 pub min_allowed_deposit: i64,
1419 pub max_allowed_deposit: i64,
1421 pub max_swapper_payable: i64,
1423 pub last_redeem_error: Option<String>,
1425 pub channel_opening_fees: Option<OpeningFeeParams>,
1431 pub confirmed_at: Option<u32>,
1433}
1434
1435#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
1437pub struct UnspentTransactionOutput {
1438 pub txid: Vec<u8>,
1439 pub outnum: u32,
1440 pub amount_millisatoshi: u64,
1441 pub address: String,
1442 #[serde(default)]
1443 pub reserved: bool,
1444}
1445
1446#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1448#[serde(tag = "buy_bitcoin_provider")]
1449pub enum BuyBitcoinProvider {
1450 Moonpay,
1451}
1452
1453#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1457pub struct PrepareRedeemOnchainFundsRequest {
1458 pub to_address: String,
1459 pub sat_per_vbyte: u32,
1460}
1461
1462#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
1465pub struct PrepareRedeemOnchainFundsResponse {
1466 pub tx_weight: u64,
1467 pub tx_fee_sat: u64,
1468}
1469
1470impl FromStr for BuyBitcoinProvider {
1471 type Err = anyhow::Error;
1472
1473 fn from_str(s: &str) -> Result<Self, Self::Err> {
1474 match s {
1475 "moonpay" => Ok(BuyBitcoinProvider::Moonpay),
1476 _ => Err(anyhow!("unknown buy bitcoin provider")),
1477 }
1478 }
1479}
1480
1481#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
1482pub struct PaymentPath {
1483 pub edges: Vec<PaymentPathEdge>,
1484}
1485
1486impl PaymentPath {
1487 pub fn final_hop_amount(&self, first_hop_amount_msat: u64) -> u64 {
1488 let mut max_to_send = first_hop_amount_msat;
1489 for h in self.edges.iter().skip(1) {
1490 max_to_send = h.amount_to_forward(max_to_send);
1491 }
1492 max_to_send
1493 }
1494
1495 pub fn first_hop_amount(&self, final_hop_amount_msat: u64) -> u64 {
1496 let mut first_hop_amount = final_hop_amount_msat;
1497 for h in self.edges.iter().skip(1).rev() {
1498 first_hop_amount = h.amount_from_forward(first_hop_amount);
1499 }
1500 first_hop_amount
1501 }
1502}
1503
1504#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
1505pub struct PaymentPathEdge {
1506 pub node_id: Vec<u8>,
1507 pub short_channel_id: String,
1508 pub channel_delay: u64,
1509 pub base_fee_msat: u64,
1510 pub fee_per_millionth: u64,
1511}
1512
1513impl PaymentPathEdge {
1514 pub(crate) fn amount_to_forward(&self, in_amount_msat: u64) -> u64 {
1515 let amount_to_forward = Self::divide_ceil(
1516 1_000_000 * (in_amount_msat - self.base_fee_msat),
1517 1_000_000 + self.fee_per_millionth,
1518 );
1519
1520 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);
1521 amount_to_forward
1522 }
1523
1524 pub(crate) fn amount_from_forward(&self, forward_amount_msat: u64) -> u64 {
1525 let in_amount_msat = self.base_fee_msat
1526 + forward_amount_msat * (1_000_000 + self.fee_per_millionth) / 1_000_000;
1527
1528 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);
1529 in_amount_msat
1530 }
1531
1532 fn divide_ceil(dividend: u64, factor: u64) -> u64 {
1533 dividend.div_ceil(factor)
1534 }
1535}
1536
1537pub(crate) mod sanitize {
1538 use crate::{FullReverseSwapInfo, SwapInfo};
1539
1540 pub(crate) trait Sanitize {
1541 fn sanitize(self) -> Self;
1543 }
1544
1545 pub(crate) fn sanitize_vec<T>(vals: Vec<T>) -> Vec<T>
1546 where
1547 T: Sanitize,
1548 {
1549 vals.into_iter()
1550 .map(|val| val.sanitize())
1551 .collect::<Vec<T>>()
1552 }
1553
1554 impl Sanitize for FullReverseSwapInfo {
1555 fn sanitize(self) -> FullReverseSwapInfo {
1556 FullReverseSwapInfo {
1557 preimage: vec![],
1558 private_key: vec![],
1559 ..self.clone()
1560 }
1561 }
1562 }
1563
1564 impl Sanitize for SwapInfo {
1565 fn sanitize(self) -> SwapInfo {
1566 SwapInfo {
1567 preimage: vec![],
1568 private_key: vec![],
1569 ..self.clone()
1570 }
1571 }
1572 }
1573}
1574
1575#[cfg(test)]
1576mod tests {
1577 use anyhow::Result;
1578 use prost::Message;
1579 use rand::random;
1580 use sdk_common::grpc;
1581
1582 use crate::models::sanitize::Sanitize;
1583 use crate::test_utils::{get_test_ofp, get_test_ofp_48h, rand_string, rand_vec_u8};
1584 use crate::{
1585 FullReverseSwapInfo, OpeningFeeParams, PaymentPath, PaymentPathEdge, ReverseSwapInfoCached,
1586 ReverseSwapStatus, SwapInfo,
1587 };
1588
1589 #[test]
1590 fn test_route_fees() -> Result<()> {
1591 let route = PaymentPath {
1592 edges: vec![
1593 PaymentPathEdge {
1594 node_id: vec![1],
1595 short_channel_id: "807189x2048x0".into(),
1596 channel_delay: 34,
1597 base_fee_msat: 1000,
1598 fee_per_millionth: 10,
1599 },
1600 PaymentPathEdge {
1601 node_id: vec![2],
1602 short_channel_id: "811871x2726x1".into(),
1603 channel_delay: 34,
1604 base_fee_msat: 0,
1605 fee_per_millionth: 0,
1606 },
1607 PaymentPathEdge {
1608 node_id: vec![3],
1609 short_channel_id: "16000000x0x18087".into(),
1610 channel_delay: 40,
1611 base_fee_msat: 1000,
1612 fee_per_millionth: 1,
1613 },
1614 ],
1615 };
1616 assert_eq!(route.final_hop_amount(1141000), 1139999);
1617 assert_eq!(route.first_hop_amount(1139999), 1141000);
1618
1619 let route = PaymentPath {
1620 edges: vec![
1621 PaymentPathEdge {
1622 node_id: vec![1],
1623 short_channel_id: "807189x2048x0".into(),
1624 channel_delay: 34,
1625 base_fee_msat: 1000,
1626 fee_per_millionth: 10,
1627 },
1628 PaymentPathEdge {
1629 node_id: vec![2],
1630 short_channel_id: "811871x2726x1".into(),
1631 channel_delay: 34,
1632 base_fee_msat: 0,
1633 fee_per_millionth: 0,
1634 },
1635 PaymentPathEdge {
1636 node_id: vec![3],
1637 short_channel_id: "16000000x0x18087".into(),
1638 channel_delay: 40,
1639 base_fee_msat: 0,
1640 fee_per_millionth: 2000,
1641 },
1642 ],
1643 };
1644 assert_eq!(route.final_hop_amount(1141314), 1139036);
1645 assert_eq!(route.first_hop_amount(1139036), 1141314);
1646
1647 Ok(())
1648 }
1649
1650 use super::OpeningFeeParamsMenu;
1651
1652 #[test]
1653 fn test_ofp_menu_validation() -> Result<()> {
1654 OpeningFeeParamsMenu::try_from(vec![get_test_ofp(10, 12, true)])?;
1656
1657 assert!(OpeningFeeParamsMenu::try_from(vec![
1659 get_test_ofp(10, 12, true),
1660 get_test_ofp(10, 12, true),
1661 ])
1662 .is_err());
1663
1664 OpeningFeeParamsMenu::try_from(vec![
1666 get_test_ofp(10, 12, true),
1667 get_test_ofp(12, 12, true),
1668 ])?;
1669
1670 OpeningFeeParamsMenu::try_from(vec![
1672 get_test_ofp(10, 12, true),
1673 get_test_ofp(10, 14, true),
1674 ])?;
1675
1676 OpeningFeeParamsMenu::try_from(vec![
1678 get_test_ofp(10, 12, true),
1679 get_test_ofp(12, 14, true),
1680 ])?;
1681
1682 assert!(OpeningFeeParamsMenu::try_from(vec![
1685 get_test_ofp(10, 12, true),
1686 get_test_ofp(10, 12, true),
1687 ])
1688 .is_err());
1689 assert!(OpeningFeeParamsMenu::try_from(vec![
1691 get_test_ofp(10, 12, true),
1692 get_test_ofp(10, 10, true),
1693 ])
1694 .is_err());
1695 assert!(OpeningFeeParamsMenu::try_from(vec![
1697 get_test_ofp(12, 10, true),
1698 get_test_ofp(10, 10, true),
1699 ])
1700 .is_err());
1701
1702 assert!(OpeningFeeParamsMenu::try_from(vec![
1704 get_test_ofp(10, 10, true),
1705 get_test_ofp(12, 12, false),
1706 ])
1707 .is_err());
1708
1709 assert!(OpeningFeeParamsMenu::try_from(vec![
1711 get_test_ofp(10, 10, false),
1712 get_test_ofp(12, 12, false),
1713 ])
1714 .is_err());
1715
1716 Ok(())
1717 }
1718
1719 #[test]
1720 fn test_payment_information_ser_de() -> Result<()> {
1721 let dummy_payment_info = grpc::PaymentInformation {
1722 payment_hash: rand_vec_u8(10),
1723 payment_secret: rand_vec_u8(10),
1724 destination: rand_vec_u8(10),
1725 incoming_amount_msat: random(),
1726 outgoing_amount_msat: random(),
1727 tag: "".to_string(),
1728 opening_fee_params: None,
1729 };
1730
1731 let mut buf = Vec::with_capacity(dummy_payment_info.encoded_len());
1732 dummy_payment_info.encode(&mut buf)?;
1733
1734 let decoded_payment_info = grpc::PaymentInformation::decode(&*buf)?;
1735
1736 assert_eq!(dummy_payment_info, decoded_payment_info);
1737
1738 Ok(())
1739 }
1740
1741 #[test]
1742 fn test_dynamic_fee_valid_until_format() -> Result<()> {
1743 let mut ofp: OpeningFeeParams = get_test_ofp(1, 1, true).into();
1744 ofp.valid_until = "2023-08-03T00:30:35.117Z".to_string();
1745 ofp.valid_until_date().map(|_| ())
1746 }
1747
1748 #[test]
1750 fn test_sanitization() -> Result<()> {
1751 let rev_swap_info_sanitized = FullReverseSwapInfo {
1752 id: "rev_swap_id".to_string(),
1753 created_at_block_height: 0,
1754 preimage: rand_vec_u8(32),
1755 private_key: vec![],
1756 claim_pubkey: "claim_pubkey".to_string(),
1757 timeout_block_height: 600_000,
1758 invoice: "645".to_string(),
1759 redeem_script: "redeem_script".to_string(),
1760 onchain_amount_sat: 250,
1761 sat_per_vbyte: Some(50),
1762 receive_amount_sat: None,
1763 cache: ReverseSwapInfoCached {
1764 status: ReverseSwapStatus::CompletedConfirmed,
1765 lockup_txid: Some("lockup_txid".to_string()),
1766 claim_txid: Some("claim_txid".to_string()),
1767 },
1768 }
1769 .sanitize();
1770 assert_eq!(rev_swap_info_sanitized.preimage, Vec::<u8>::new());
1771 assert_eq!(rev_swap_info_sanitized.private_key, Vec::<u8>::new());
1772
1773 let swap_info_sanitized = SwapInfo {
1774 bitcoin_address: rand_string(10),
1775 created_at: 10,
1776 lock_height: random(),
1777 payment_hash: rand_vec_u8(32),
1778 preimage: rand_vec_u8(32),
1779 private_key: rand_vec_u8(32),
1780 public_key: rand_vec_u8(10),
1781 swapper_public_key: rand_vec_u8(10),
1782 script: rand_vec_u8(10),
1783 bolt11: None,
1784 paid_msat: 0,
1785 unconfirmed_sats: 0,
1786 confirmed_sats: 0,
1787 total_incoming_txs: 0,
1788 status: crate::models::SwapStatus::Initial,
1789 refund_tx_ids: Vec::new(),
1790 unconfirmed_tx_ids: Vec::new(),
1791 confirmed_tx_ids: Vec::new(),
1792 min_allowed_deposit: 0,
1793 max_allowed_deposit: 100,
1794 max_swapper_payable: 200,
1795 last_redeem_error: None,
1796 channel_opening_fees: Some(get_test_ofp_48h(random(), random()).into()),
1797 confirmed_at: None,
1798 }
1799 .sanitize();
1800 assert_eq!(swap_info_sanitized.preimage, Vec::<u8>::new());
1801 assert_eq!(swap_info_sanitized.private_key, Vec::<u8>::new());
1802
1803 Ok(())
1804 }
1805}