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