1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::str::FromStr;
4use std::time::{Duration, SystemTime};
5
6use bitcoin::address::NetworkUnchecked;
7use bitcoin::hashes::sha256;
8use bitcoin::secp256k1::PublicKey;
9use bitcoin::{Address, Network, OutPoint};
10use clap::Subcommand;
11use envs::{
12 FM_LDK_ALIAS_ENV, FM_LND_MACAROON_ENV, FM_LND_PAYMENT_TIMEOUT_SECS_ENV, FM_LND_RPC_ADDR_ENV,
13 FM_LND_TIME_PREF_ENV, FM_LND_TLS_CERT_ENV, FM_PORT_LDK,
14};
15use fedimint_core::config::{FederationId, JsonClientConfig};
16use fedimint_core::encoding::{Decodable, Encodable};
17use fedimint_core::invite_code::InviteCode;
18use fedimint_core::util::{SafeUrl, get_average, get_median};
19use fedimint_core::{Amount, BitcoinAmountOrAll, secp256k1};
20use fedimint_eventlog::{EventKind, EventLogId, PersistedLogEntry, StructuredPaymentEvents};
21use fedimint_lnv2_common::gateway_api::PaymentFee;
22use fedimint_wallet_client::PegOutFees;
23use lightning::ln::msgs::SocketAddress;
24use lightning_invoice::Bolt11Invoice;
25use serde::de::Error as _;
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27
28pub mod envs;
29pub mod federation_status;
30
31pub use federation_status::*;
32
33pub const V1_API_ENDPOINT: &str = "v1";
34
35pub const ADDRESS_ENDPOINT: &str = "/address";
36pub const ADDRESS_RECHECK_ENDPOINT: &str = "/address_recheck";
37pub const BACKUP_ENDPOINT: &str = "/backup";
38pub const CONFIGURATION_ENDPOINT: &str = "/config";
39pub const CONNECT_FED_ENDPOINT: &str = "/connect_fed";
40pub const CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt11_invoice_for_operator";
41pub const CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt12_offer_for_operator";
42pub const FEDERATION_STATUS_ENDPOINT: &str = "/federation_status";
43pub const GATEWAY_INFO_ENDPOINT: &str = "/info";
44pub const INVITE_CODES_ENDPOINT: &str = "/invite_codes";
45pub const GET_BALANCES_ENDPOINT: &str = "/balances";
46pub const GET_INVOICE_ENDPOINT: &str = "/get_invoice";
47pub const GET_LN_ONCHAIN_ADDRESS_ENDPOINT: &str = "/get_ln_onchain_address";
48pub const LEAVE_FED_ENDPOINT: &str = "/leave_fed";
49pub const LIST_CHANNELS_ENDPOINT: &str = "/list_channels";
50pub const LIST_TRANSACTIONS_ENDPOINT: &str = "/list_transactions";
51pub const MNEMONIC_ENDPOINT: &str = "/mnemonic";
52pub const CONNECT_PEER_ENDPOINT: &str = "/connect_peer";
53pub const OPEN_CHANNEL_ENDPOINT: &str = "/open_channel";
54pub const OPEN_CHANNEL_WITH_PUSH_ENDPOINT: &str = "/open_channel_with_push";
55pub const CLOSE_CHANNELS_WITH_PEER_ENDPOINT: &str = "/close_channels_with_peer";
56pub const PAY_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/pay_invoice_for_operator";
57pub const PAY_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/pay_offer_for_operator";
58pub const PAYMENT_LOG_ENDPOINT: &str = "/payment_log";
59pub const PAYMENT_SUMMARY_ENDPOINT: &str = "/payment_summary";
60pub const PEGIN_FROM_ONCHAIN_ENDPOINT: &str = "/pegin_from_onchain";
61pub const RECEIVE_ECASH_ENDPOINT: &str = "/receive_ecash";
62pub const SET_CHANNEL_FEES_ENDPOINT: &str = "/set_channel_fees";
63pub const SET_FEES_ENDPOINT: &str = "/set_fees";
64pub const SET_PAYMENT_POLICY_ENDPOINT: &str = "/set_payment_policy";
65pub const STOP_ENDPOINT: &str = "/stop";
66pub const SEND_ONCHAIN_ENDPOINT: &str = "/send_onchain";
67pub const SPEND_ECASH_ENDPOINT: &str = "/spend_ecash";
68pub const WITHDRAW_ENDPOINT: &str = "/withdraw";
69pub const WITHDRAW_TO_ONCHAIN_ENDPOINT: &str = "/withdraw_to_onchain";
70
71pub const DEFAULT_LIGHTNING_PORT: u16 = 9735;
72
73#[derive(Debug, Serialize, Deserialize, Clone)]
74pub struct ConnectFedPayload {
75 pub invite_code: String,
76 pub use_tor: Option<bool>,
77 pub recover: Option<bool>,
78}
79
80#[derive(Debug, Serialize, Deserialize, Clone)]
81pub struct LeaveFedPayload {
82 pub federation_id: FederationId,
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86pub struct InfoPayload;
87
88#[derive(Debug, Serialize, Deserialize)]
89pub struct BackupPayload {
90 pub federation_id: FederationId,
91}
92
93#[derive(Debug, Serialize, Deserialize, Clone)]
94pub struct ConfigPayload {
95 pub federation_id: Option<FederationId>,
96}
97
98#[derive(Debug, Serialize, Deserialize, Clone)]
99pub struct DepositAddressPayload {
100 pub federation_id: FederationId,
101}
102
103#[derive(Debug, Serialize, Deserialize, Clone)]
104pub struct PeginFromOnchainPayload {
105 pub federation_id: FederationId,
106 pub amount: BitcoinAmountOrAll,
107 pub fee_rate_sats_per_vbyte: u64,
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone)]
111pub struct DepositAddressRecheckPayload {
112 pub address: Address<NetworkUnchecked>,
113 pub federation_id: FederationId,
114}
115
116#[derive(Debug, Serialize, Deserialize, Clone)]
117pub struct WithdrawPayload {
118 pub federation_id: FederationId,
119 pub amount: BitcoinAmountOrAll,
120 pub address: Address<NetworkUnchecked>,
121 #[serde(default)]
124 pub quoted_fees: Option<PegOutFees>,
125}
126
127#[derive(Debug, Serialize, Deserialize, Clone)]
128pub struct WithdrawToOnchainPayload {
129 pub federation_id: FederationId,
130 pub amount: BitcoinAmountOrAll,
131}
132
133#[derive(Debug, Serialize, Deserialize, Clone)]
134pub struct WithdrawResponse {
135 pub txid: bitcoin::Txid,
136 pub fees: PegOutFees,
137}
138
139#[derive(Debug, Serialize, Deserialize, Clone)]
140pub struct WithdrawPreviewPayload {
141 pub federation_id: FederationId,
142 pub amount: BitcoinAmountOrAll,
143 pub address: Address<NetworkUnchecked>,
144}
145
146#[derive(Debug, Serialize, Deserialize, Clone)]
147pub struct WithdrawPreviewResponse {
148 pub withdraw_amount: Amount,
149 pub address: String,
150 pub peg_out_fees: PegOutFees,
151 pub total_cost: Amount,
152 #[serde(default)]
156 pub mint_fees: Option<Amount>,
157 #[serde(default)]
160 pub flat_peg_out_fee: bool,
161}
162
163#[allow(deprecated)]
167#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
168pub enum ConnectorType {
169 Tcp,
170 Tor,
171}
172
173#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
174pub struct FederationConfig {
175 pub invite_code: InviteCode,
176 #[serde(alias = "mint_channel_id")]
179 pub federation_index: u64,
180 pub lightning_fee: PaymentFee,
181 pub transaction_fee: PaymentFee,
182 #[serde(default)]
189 pub payment_policies: BTreeSet<PaymentPolicy>,
190 #[allow(deprecated)] pub _connector: ConnectorType,
192}
193
194impl FederationConfig {
195 pub fn receive_enabled(&self) -> bool {
198 !self
199 .payment_policies
200 .contains(&PaymentPolicy::ReceivesDisabled)
201 }
202}
203
204#[derive(
207 Debug,
208 Clone,
209 Copy,
210 Eq,
211 PartialEq,
212 Ord,
213 PartialOrd,
214 Hash,
215 Encodable,
216 Decodable,
217 Serialize,
218 Deserialize,
219)]
220pub enum PaymentPolicy {
221 ReceivesDisabled,
225}
226
227#[derive(Debug)]
235pub struct PaymentPolicyGuard(#[allow(dead_code)] tokio::sync::OwnedRwLockReadGuard<()>);
236
237impl PaymentPolicyGuard {
238 pub fn new(guard: tokio::sync::OwnedRwLockReadGuard<()>) -> Self {
239 Self(guard)
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
245pub struct FederationInfo {
246 pub federation_id: FederationId,
247 pub federation_name: Option<String>,
248 pub balance_msat: Amount,
249 pub config: FederationConfig,
250 pub last_backup_time: Option<SystemTime>,
251}
252
253#[derive(Debug, Serialize, Deserialize, PartialEq)]
254pub struct GatewayInfo {
255 pub version_hash: String,
256 pub federations: Vec<FederationInfo>,
257 #[serde(alias = "channels")]
260 pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
261 pub gateway_state: String,
262 pub lightning_info: LightningInfo,
263 pub lightning_mode: LightningMode,
264 pub registrations: BTreeMap<RegisteredProtocol, (SafeUrl, secp256k1::PublicKey)>,
265}
266
267#[derive(Debug, Serialize, Deserialize, PartialEq)]
268pub struct GatewayFedConfig {
269 pub federations: BTreeMap<FederationId, JsonClientConfig>,
270}
271
272#[derive(Debug, Serialize, Deserialize, Clone)]
273pub struct SetFeesPayload {
274 pub federation_id: Option<FederationId>,
275 pub lightning_base: Option<Amount>,
276 pub lightning_parts_per_million: Option<u64>,
277 pub transaction_base: Option<Amount>,
278 pub transaction_parts_per_million: Option<u64>,
279}
280
281#[derive(Debug, Serialize, Deserialize, Clone)]
285pub struct SetPaymentPolicyPayload {
286 pub federation_id: Option<FederationId>,
288 pub receive_enabled: Option<bool>,
292}
293
294#[derive(Debug, Serialize, Deserialize, Clone)]
295pub struct CreateInvoiceForOperatorPayload {
296 pub amount_msats: u64,
297 pub expiry_secs: Option<u32>,
298 pub description: Option<String>,
299}
300
301#[derive(Debug, Serialize, Deserialize, Clone)]
302pub struct PayInvoiceForOperatorPayload {
303 pub invoice: Bolt11Invoice,
304}
305
306#[derive(Debug, Serialize, Deserialize, Clone)]
307pub struct SpendEcashPayload {
308 pub federation_id: FederationId,
310 pub amount: Amount,
312}
313
314#[derive(Debug, Serialize, Deserialize, Clone)]
315pub struct SpendEcashResponse {
316 pub notes: String,
318}
319
320#[derive(Debug, Serialize, Deserialize, Clone)]
321pub struct ReceiveEcashPayload {
322 pub notes: String,
324}
325
326#[derive(Debug, Serialize, Deserialize, Clone)]
327pub struct ReceiveEcashResponse {
328 pub amount: Amount,
329}
330
331#[derive(serde::Serialize, serde::Deserialize, Clone)]
332pub struct GatewayBalances {
333 pub onchain_balance_sats: u64,
334 pub lightning_balance_msats: u64,
335 pub ecash_balances: Vec<FederationBalanceInfo>,
336 pub inbound_lightning_liquidity_msats: u64,
337}
338
339#[derive(serde::Serialize, serde::Deserialize, Clone)]
340pub struct FederationBalanceInfo {
341 pub federation_id: FederationId,
342 pub ecash_balance_msats: Amount,
343}
344
345#[derive(Debug, Serialize, Deserialize, Clone)]
346pub struct MnemonicResponse {
347 pub mnemonic: Vec<String>,
348
349 pub legacy_federations: Vec<FederationId>,
353}
354
355#[derive(Debug, Serialize, Deserialize, Clone)]
356pub struct PaymentLogPayload {
357 pub end_position: Option<EventLogId>,
360
361 pub pagination_size: usize,
363
364 pub federation_id: FederationId,
365
366 pub event_kinds: Vec<EventKind>,
374}
375
376#[derive(Debug, Serialize, Deserialize, Clone)]
377pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
378
379#[derive(Debug, Serialize, Deserialize, Clone)]
380pub struct PaymentSummaryResponse {
381 pub outgoing: PaymentStats,
382 pub incoming: PaymentStats,
383}
384
385#[derive(Debug, Serialize, Deserialize, Clone)]
386pub struct PaymentStats {
387 pub average_latency: Option<Duration>,
388 pub median_latency: Option<Duration>,
389 pub total_fees: Amount,
390 pub total_success: usize,
391 pub total_failure: usize,
392}
393
394impl PaymentStats {
395 pub fn compute(events: &StructuredPaymentEvents) -> Self {
397 PaymentStats {
398 average_latency: get_average(&events.latencies_usecs).map(Duration::from_micros),
399 median_latency: get_median(&events.latencies_usecs).map(Duration::from_micros),
400 total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
401 total_success: events.latencies_usecs.len(),
402 total_failure: events.latencies_failure.len(),
403 }
404 }
405}
406
407#[derive(Debug, Serialize, Deserialize, Clone)]
408pub struct PaymentSummaryPayload {
409 pub start_millis: u64,
410 pub end_millis: u64,
411}
412
413#[derive(Serialize, Deserialize, Debug, Clone)]
414pub struct ChannelInfo {
415 pub remote_pubkey: secp256k1::PublicKey,
416 pub channel_size_sats: u64,
417 pub outbound_liquidity_sats: u64,
418 pub inbound_liquidity_sats: u64,
419 pub is_active: bool,
420 pub funding_outpoint: Option<OutPoint>,
421 pub remote_node_alias: Option<String>,
422 #[serde(default)]
423 pub remote_address: Option<String>,
424 #[serde(default)]
427 pub base_fee_msat: Option<u64>,
428 #[serde(default)]
432 pub parts_per_million: Option<u64>,
433}
434
435#[derive(Debug, Serialize, Deserialize, Clone)]
436pub struct OpenChannelRequest {
437 pub pubkey: secp256k1::PublicKey,
438 pub host: String,
439 pub channel_size_sats: u64,
440 pub push_amount_sats: u64,
441 #[serde(default, deserialize_with = "empty_string_as_none")]
445 pub fee_rate_sats_per_vbyte: Option<u64>,
446 #[serde(default, deserialize_with = "empty_string_as_none")]
449 pub base_fee_msat: Option<u64>,
450 #[serde(default, deserialize_with = "empty_string_as_none")]
453 pub parts_per_million: Option<u64>,
454}
455
456#[derive(Debug, Serialize, Deserialize, Clone)]
457pub struct ConnectPeerRequest {
458 pub node_address: NodeAddress,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct NodeAddress {
463 pub pubkey: secp256k1::PublicKey,
464 pub address: SocketAddress,
465}
466
467impl NodeAddress {
468 pub fn host_with_port(&self) -> String {
469 self.address.to_string()
470 }
471}
472
473impl FromStr for NodeAddress {
474 type Err = String;
475
476 fn from_str(input: &str) -> Result<Self, Self::Err> {
477 let (pubkey, address) = input
478 .trim()
479 .split_once('@')
480 .ok_or_else(|| "Expected node address in pubkey@host[:port] format".to_string())?;
481 let pubkey = pubkey
482 .parse()
483 .map_err(|err| format!("Invalid peer public key: {err}"))?;
484 let address = address.trim();
485 if address.is_empty() {
486 return Err("Peer host must not be empty".to_string());
487 }
488 let address = if has_explicit_port(address) {
489 address.to_string()
490 } else {
491 format!("{address}:{DEFAULT_LIGHTNING_PORT}")
492 };
493 let address = SocketAddress::from_str(&address)
494 .map_err(|err| format!("Invalid peer address: {err}"))?;
495
496 Ok(NodeAddress { pubkey, address })
497 }
498}
499
500impl fmt::Display for NodeAddress {
501 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 let address = self.address.to_string();
503 if address.ends_with(&format!(":{DEFAULT_LIGHTNING_PORT}")) {
504 write!(
505 f,
506 "{}@{}",
507 self.pubkey,
508 &address[..address.len() - format!(":{DEFAULT_LIGHTNING_PORT}").len()]
509 )
510 } else {
511 write!(f, "{}@{}", self.pubkey, address)
512 }
513 }
514}
515
516fn has_explicit_port(address: &str) -> bool {
517 if address.starts_with('[') {
518 return address.contains("]:");
519 }
520
521 address.contains(':')
522}
523
524impl Serialize for NodeAddress {
525 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
526 where
527 S: Serializer,
528 {
529 serializer.serialize_str(&self.to_string())
530 }
531}
532
533impl<'de> Deserialize<'de> for NodeAddress {
534 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
535 where
536 D: Deserializer<'de>,
537 {
538 String::deserialize(deserializer)?
539 .parse()
540 .map_err(D::Error::custom)
541 }
542}
543
544#[derive(Debug, Serialize, Deserialize, Clone)]
545pub struct SetChannelFeesRequest {
546 pub funding_outpoint: OutPoint,
549 pub base_fee_msat: u64,
551 pub parts_per_million: u64,
553}
554
555fn empty_string_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
559where
560 D: serde::Deserializer<'de>,
561 T: std::str::FromStr + Deserialize<'de>,
562 T::Err: std::fmt::Display,
563{
564 #[derive(Deserialize)]
565 #[serde(untagged)]
566 enum StringOrT<T> {
567 String(String),
568 T(T),
569 }
570
571 match Option::<StringOrT<T>>::deserialize(deserializer)? {
572 None => Ok(None),
573 Some(StringOrT::T(value)) => Ok(Some(value)),
574 Some(StringOrT::String(s)) if s.trim().is_empty() => Ok(None),
575 Some(StringOrT::String(s)) => s
576 .trim()
577 .parse::<T>()
578 .map(Some)
579 .map_err(serde::de::Error::custom),
580 }
581}
582
583#[derive(Debug, Serialize, Deserialize, Clone)]
584pub struct SendOnchainRequest {
585 pub address: Address<NetworkUnchecked>,
586 pub amount: BitcoinAmountOrAll,
587 pub fee_rate_sats_per_vbyte: u64,
588}
589
590impl fmt::Display for SendOnchainRequest {
591 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
592 write!(
593 f,
594 "SendOnchainRequest {{ address: {}, amount: {}, fee_rate_sats_per_vbyte: {} }}",
595 self.address.assume_checked_ref(),
596 self.amount,
597 self.fee_rate_sats_per_vbyte
598 )
599 }
600}
601
602#[derive(Debug, Serialize, Deserialize, Clone)]
603pub struct CloseChannelsWithPeerRequest {
604 pub pubkey: secp256k1::PublicKey,
605 #[serde(default)]
606 pub force: bool,
607 pub sats_per_vbyte: Option<u64>,
608}
609
610impl fmt::Display for CloseChannelsWithPeerRequest {
611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612 write!(
613 f,
614 "CloseChannelsWithPeerRequest {{ pubkey: {}, force: {}, sats_per_vbyte: {} }}",
615 self.pubkey,
616 self.force,
617 match self.sats_per_vbyte {
618 Some(sats) => sats.to_string(),
619 None => "None".to_string(),
620 }
621 )
622 }
623}
624
625#[derive(Debug, Serialize, Deserialize, Clone)]
626pub struct CloseChannelsWithPeerResponse {
627 pub num_channels_closed: u32,
628}
629
630#[derive(Debug, Serialize, Deserialize, Clone)]
631pub struct GetInvoiceRequest {
632 pub payment_hash: sha256::Hash,
633}
634
635#[derive(Debug, Serialize, Deserialize, Clone)]
636pub struct GetInvoiceResponse {
637 pub preimage: Option<String>,
638 pub payment_hash: Option<sha256::Hash>,
639 pub amount: Amount,
640 pub created_at: SystemTime,
641 pub status: PaymentStatus,
642}
643
644#[derive(Debug, Serialize, Deserialize, Clone)]
645pub struct ListTransactionsPayload {
646 pub start_secs: u64,
647 pub end_secs: u64,
648}
649
650#[derive(Debug, Serialize, Deserialize, Clone)]
651pub struct ListTransactionsResponse {
652 pub transactions: Vec<PaymentDetails>,
653}
654
655#[derive(Debug, Serialize, Deserialize, Clone)]
656pub struct PaymentDetails {
657 pub payment_hash: Option<sha256::Hash>,
658 pub preimage: Option<String>,
659 pub payment_kind: PaymentKind,
660 pub amount: Amount,
661 pub direction: PaymentDirection,
662 pub status: PaymentStatus,
663 pub timestamp_secs: u64,
664}
665
666#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
667pub enum PaymentKind {
668 Bolt11,
669 Bolt12Offer,
670 Bolt12Refund,
671 Onchain,
672}
673
674#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
675pub enum PaymentDirection {
676 Outbound,
677 Inbound,
678}
679
680#[derive(Debug, Serialize, Deserialize, Clone)]
681pub struct CreateOfferPayload {
682 pub amount: Option<Amount>,
683 pub description: Option<String>,
684 pub expiry_secs: Option<u32>,
685 pub quantity: Option<u64>,
686}
687
688#[derive(Debug, Serialize, Deserialize, Clone)]
689pub struct CreateOfferResponse {
690 pub offer: String,
691}
692
693#[derive(Debug, Serialize, Deserialize, Clone)]
694pub struct PayOfferPayload {
695 pub offer: String,
696 pub amount: Option<Amount>,
697 pub quantity: Option<u64>,
698 pub payer_note: Option<String>,
699}
700
701#[derive(Debug, Serialize, Deserialize, Clone)]
702pub struct PayOfferResponse {
703 pub preimage: String,
704}
705
706#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
707pub enum PaymentStatus {
708 Pending,
709 Succeeded,
710 Failed,
711}
712
713pub const LND_DEFAULT_TIME_PREF: f64 = 0.5;
718pub const LND_TIME_PREF_MIN: f64 = -1.0;
719pub const LND_TIME_PREF_MAX: f64 = 1.0;
720
721fn parse_lnd_time_pref(s: &str) -> Result<f64, String> {
722 let parsed: f64 = s.parse().map_err(|e| format!("invalid f64: {e}"))?;
723 if !parsed.is_finite() || !(LND_TIME_PREF_MIN..=LND_TIME_PREF_MAX).contains(&parsed) {
724 return Err(format!(
725 "must be a finite value in [{LND_TIME_PREF_MIN}, {LND_TIME_PREF_MAX}]"
726 ));
727 }
728 Ok(parsed)
729}
730
731pub const LND_DEFAULT_PAYMENT_TIMEOUT_SECS: i32 = 180;
735pub const LND_PAYMENT_TIMEOUT_SECS_MIN: i32 = 1;
736pub const LND_PAYMENT_TIMEOUT_SECS_MAX: i32 = 600;
737
738fn parse_lnd_payment_timeout_secs(s: &str) -> Result<i32, String> {
739 let parsed: i32 = s.parse().map_err(|e| format!("invalid i32: {e}"))?;
740 if !(LND_PAYMENT_TIMEOUT_SECS_MIN..=LND_PAYMENT_TIMEOUT_SECS_MAX).contains(&parsed) {
741 return Err(format!(
742 "must be an integer in [{LND_PAYMENT_TIMEOUT_SECS_MIN}, {LND_PAYMENT_TIMEOUT_SECS_MAX}]"
743 ));
744 }
745 Ok(parsed)
746}
747
748#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, PartialEq)]
749pub enum LightningMode {
750 #[clap(name = "lnd")]
751 Lnd {
752 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
754 lnd_rpc_addr: String,
755
756 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
758 lnd_tls_cert: String,
759
760 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
762 lnd_macaroon: String,
763
764 #[arg(
767 long = "lnd-time-pref",
768 env = FM_LND_TIME_PREF_ENV,
769 default_value_t = LND_DEFAULT_TIME_PREF,
770 value_parser = parse_lnd_time_pref,
771 )]
772 lnd_time_pref: f64,
773
774 #[arg(
778 long = "lnd-payment-timeout",
779 env = FM_LND_PAYMENT_TIMEOUT_SECS_ENV,
780 default_value_t = LND_DEFAULT_PAYMENT_TIMEOUT_SECS,
781 value_parser = parse_lnd_payment_timeout_secs,
782 )]
783 lnd_payment_timeout_secs: i32,
784 },
785 #[clap(name = "ldk")]
786 Ldk {
787 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
789 lightning_port: u16,
790
791 #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
793 alias: String,
794 },
795}
796
797#[derive(Clone)]
798pub enum ChainSource {
799 Bitcoind {
800 username: String,
801 password: String,
802 server_url: SafeUrl,
803 },
804 Esplora {
805 server_url: SafeUrl,
806 },
807}
808
809impl fmt::Display for ChainSource {
810 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811 match self {
812 ChainSource::Bitcoind {
813 username: _,
814 password: _,
815 server_url,
816 } => {
817 write!(f, "Bitcoind source with URL: {server_url}")
818 }
819 ChainSource::Esplora { server_url } => {
820 write!(f, "Esplora source with URL: {server_url}")
821 }
822 }
823 }
824}
825
826#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
827#[serde(rename_all = "snake_case")]
828pub enum LightningInfo {
829 Connected {
830 public_key: PublicKey,
831 alias: String,
832 network: Network,
833 block_height: u64,
834 synced_to_chain: bool,
835 },
836 NotConnected,
837}
838
839#[derive(
840 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Encodable, Decodable, Serialize, Deserialize,
841)]
842#[serde(rename_all = "snake_case")]
843pub enum RegisteredProtocol {
844 Http,
845 Iroh,
846}
847
848#[derive(Debug, Serialize, Deserialize, Clone)]
849pub struct SetMnemonicPayload {
850 pub words: Option<String>,
851}
852
853#[cfg(test)]
854mod tests;