Skip to main content

fedimint_gateway_common/
lib.rs

1use std::collections::BTreeMap;
2use std::fmt;
3use std::time::{Duration, SystemTime};
4
5use bitcoin::address::NetworkUnchecked;
6use bitcoin::hashes::sha256;
7use bitcoin::secp256k1::PublicKey;
8use bitcoin::{Address, Network, OutPoint};
9use clap::Subcommand;
10use envs::{
11    FM_LDK_ALIAS_ENV, FM_LND_MACAROON_ENV, FM_LND_RPC_ADDR_ENV, FM_LND_TLS_CERT_ENV, FM_PORT_LDK,
12};
13use fedimint_core::config::{FederationId, JsonClientConfig};
14use fedimint_core::encoding::{Decodable, Encodable};
15use fedimint_core::invite_code::InviteCode;
16use fedimint_core::util::{SafeUrl, get_average, get_median};
17use fedimint_core::{Amount, BitcoinAmountOrAll, secp256k1};
18use fedimint_eventlog::{EventKind, EventLogId, PersistedLogEntry, StructuredPaymentEvents};
19use fedimint_lnv2_common::gateway_api::PaymentFee;
20use fedimint_wallet_client::PegOutFees;
21use lightning_invoice::Bolt11Invoice;
22use serde::{Deserialize, Serialize};
23
24pub mod envs;
25
26pub const V1_API_ENDPOINT: &str = "v1";
27
28pub const ADDRESS_ENDPOINT: &str = "/address";
29pub const ADDRESS_RECHECK_ENDPOINT: &str = "/address_recheck";
30pub const BACKUP_ENDPOINT: &str = "/backup";
31pub const CONFIGURATION_ENDPOINT: &str = "/config";
32pub const CONNECT_FED_ENDPOINT: &str = "/connect_fed";
33pub const CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt11_invoice_for_operator";
34pub const CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt12_offer_for_operator";
35pub const GATEWAY_INFO_ENDPOINT: &str = "/info";
36pub const INVITE_CODES_ENDPOINT: &str = "/invite_codes";
37pub const GET_BALANCES_ENDPOINT: &str = "/balances";
38pub const GET_INVOICE_ENDPOINT: &str = "/get_invoice";
39pub const GET_LN_ONCHAIN_ADDRESS_ENDPOINT: &str = "/get_ln_onchain_address";
40pub const LEAVE_FED_ENDPOINT: &str = "/leave_fed";
41pub const LIST_CHANNELS_ENDPOINT: &str = "/list_channels";
42pub const LIST_TRANSACTIONS_ENDPOINT: &str = "/list_transactions";
43pub const MNEMONIC_ENDPOINT: &str = "/mnemonic";
44pub const OPEN_CHANNEL_ENDPOINT: &str = "/open_channel";
45pub const OPEN_CHANNEL_WITH_PUSH_ENDPOINT: &str = "/open_channel_with_push";
46pub const CLOSE_CHANNELS_WITH_PEER_ENDPOINT: &str = "/close_channels_with_peer";
47pub const PAY_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/pay_invoice_for_operator";
48pub const PAY_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/pay_offer_for_operator";
49pub const PAYMENT_LOG_ENDPOINT: &str = "/payment_log";
50pub const PAYMENT_SUMMARY_ENDPOINT: &str = "/payment_summary";
51pub const PEGIN_FROM_ONCHAIN_ENDPOINT: &str = "/pegin_from_onchain";
52pub const RECEIVE_ECASH_ENDPOINT: &str = "/receive_ecash";
53pub const SET_FEES_ENDPOINT: &str = "/set_fees";
54pub const STOP_ENDPOINT: &str = "/stop";
55pub const SEND_ONCHAIN_ENDPOINT: &str = "/send_onchain";
56pub const SPEND_ECASH_ENDPOINT: &str = "/spend_ecash";
57pub const WITHDRAW_ENDPOINT: &str = "/withdraw";
58pub const WITHDRAW_TO_ONCHAIN_ENDPOINT: &str = "/withdraw_to_onchain";
59
60#[derive(Debug, Serialize, Deserialize, Clone)]
61pub struct ConnectFedPayload {
62    pub invite_code: String,
63    pub use_tor: Option<bool>,
64    pub recover: Option<bool>,
65}
66
67#[derive(Debug, Serialize, Deserialize, Clone)]
68pub struct LeaveFedPayload {
69    pub federation_id: FederationId,
70}
71
72#[derive(Debug, Serialize, Deserialize)]
73pub struct InfoPayload;
74
75#[derive(Debug, Serialize, Deserialize)]
76pub struct BackupPayload {
77    pub federation_id: FederationId,
78}
79
80#[derive(Debug, Serialize, Deserialize, Clone)]
81pub struct ConfigPayload {
82    pub federation_id: Option<FederationId>,
83}
84
85#[derive(Debug, Serialize, Deserialize, Clone)]
86pub struct DepositAddressPayload {
87    pub federation_id: FederationId,
88}
89
90#[derive(Debug, Serialize, Deserialize, Clone)]
91pub struct PeginFromOnchainPayload {
92    pub federation_id: FederationId,
93    pub amount: BitcoinAmountOrAll,
94    pub fee_rate_sats_per_vbyte: u64,
95}
96
97#[derive(Debug, Serialize, Deserialize, Clone)]
98pub struct DepositAddressRecheckPayload {
99    pub address: Address<NetworkUnchecked>,
100    pub federation_id: FederationId,
101}
102
103#[derive(Debug, Serialize, Deserialize, Clone)]
104pub struct WithdrawPayload {
105    pub federation_id: FederationId,
106    pub amount: BitcoinAmountOrAll,
107    pub address: Address<NetworkUnchecked>,
108    /// When provided (from UI preview flow), uses these quoted fees.
109    /// When None, fetches current fees from the wallet.
110    #[serde(default)]
111    pub quoted_fees: Option<PegOutFees>,
112}
113
114#[derive(Debug, Serialize, Deserialize, Clone)]
115pub struct WithdrawToOnchainPayload {
116    pub federation_id: FederationId,
117    pub amount: BitcoinAmountOrAll,
118}
119
120#[derive(Debug, Serialize, Deserialize, Clone)]
121pub struct WithdrawResponse {
122    pub txid: bitcoin::Txid,
123    pub fees: PegOutFees,
124}
125
126#[derive(Debug, Serialize, Deserialize, Clone)]
127pub struct WithdrawPreviewPayload {
128    pub federation_id: FederationId,
129    pub amount: BitcoinAmountOrAll,
130    pub address: Address<NetworkUnchecked>,
131}
132
133#[derive(Debug, Serialize, Deserialize, Clone)]
134pub struct WithdrawPreviewResponse {
135    pub withdraw_amount: Amount,
136    pub address: String,
137    pub peg_out_fees: PegOutFees,
138    pub total_cost: Amount,
139    /// Estimated mint fees when withdrawing all. None for partial withdrawals.
140    #[serde(default)]
141    pub mint_fees: Option<Amount>,
142}
143
144/// Deprecated, unused, doesn't do anything
145///
146/// Only here for backward-compat reasons.
147#[allow(deprecated)]
148#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
149pub enum ConnectorType {
150    Tcp,
151    Tor,
152}
153
154#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
155pub struct FederationConfig {
156    pub invite_code: InviteCode,
157    // Unique integer identifier per-federation that is assigned when the gateways joins a
158    // federation.
159    #[serde(alias = "mint_channel_id")]
160    pub federation_index: u64,
161    pub lightning_fee: PaymentFee,
162    pub transaction_fee: PaymentFee,
163    #[allow(deprecated)] // only here for decoding backward-compat
164    pub _connector: ConnectorType,
165}
166
167/// Information about one of the feds we are connected to
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
169pub struct FederationInfo {
170    pub federation_id: FederationId,
171    pub federation_name: Option<String>,
172    pub balance_msat: Amount,
173    pub config: FederationConfig,
174    pub last_backup_time: Option<SystemTime>,
175}
176
177#[derive(Debug, Serialize, Deserialize, PartialEq)]
178pub struct GatewayInfo {
179    pub version_hash: String,
180    pub federations: Vec<FederationInfo>,
181    /// Mapping from short channel id to the federation id that it belongs to.
182    // TODO: Remove this alias once it no longer breaks backwards compatibility.
183    #[serde(alias = "channels")]
184    pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
185    pub gateway_state: String,
186    pub lightning_info: LightningInfo,
187    pub lightning_mode: LightningMode,
188    pub registrations: BTreeMap<RegisteredProtocol, (SafeUrl, secp256k1::PublicKey)>,
189}
190
191#[derive(Debug, Serialize, Deserialize, PartialEq)]
192pub struct GatewayFedConfig {
193    pub federations: BTreeMap<FederationId, JsonClientConfig>,
194}
195
196#[derive(Debug, Serialize, Deserialize, Clone)]
197pub struct SetFeesPayload {
198    pub federation_id: Option<FederationId>,
199    pub lightning_base: Option<Amount>,
200    pub lightning_parts_per_million: Option<u64>,
201    pub transaction_base: Option<Amount>,
202    pub transaction_parts_per_million: Option<u64>,
203}
204
205#[derive(Debug, Serialize, Deserialize, Clone)]
206pub struct CreateInvoiceForOperatorPayload {
207    pub amount_msats: u64,
208    pub expiry_secs: Option<u32>,
209    pub description: Option<String>,
210}
211
212#[derive(Debug, Serialize, Deserialize, Clone)]
213pub struct PayInvoiceForOperatorPayload {
214    pub invoice: Bolt11Invoice,
215}
216
217#[derive(Debug, Serialize, Deserialize, Clone)]
218pub struct SpendEcashPayload {
219    /// Federation id of the e-cash to spend
220    pub federation_id: FederationId,
221    /// The amount of e-cash to spend
222    pub amount: Amount,
223}
224
225#[derive(Debug, Serialize, Deserialize, Clone)]
226pub struct SpendEcashResponse {
227    /// OOBNotes.to_string() for v1, base32::encode_prefixed() for v2
228    pub notes: String,
229}
230
231#[derive(Debug, Serialize, Deserialize, Clone)]
232pub struct ReceiveEcashPayload {
233    /// Can be OOBNotes (v1) or ECash (v2)
234    pub notes: String,
235    #[serde(default)]
236    pub wait: bool,
237}
238
239#[derive(Debug, Serialize, Deserialize, Clone)]
240pub struct ReceiveEcashResponse {
241    pub amount: Amount,
242}
243
244#[derive(serde::Serialize, serde::Deserialize, Clone)]
245pub struct GatewayBalances {
246    pub onchain_balance_sats: u64,
247    pub lightning_balance_msats: u64,
248    pub ecash_balances: Vec<FederationBalanceInfo>,
249    pub inbound_lightning_liquidity_msats: u64,
250}
251
252#[derive(serde::Serialize, serde::Deserialize, Clone)]
253pub struct FederationBalanceInfo {
254    pub federation_id: FederationId,
255    pub ecash_balance_msats: Amount,
256}
257
258#[derive(Debug, Serialize, Deserialize, Clone)]
259pub struct MnemonicResponse {
260    pub mnemonic: Vec<String>,
261
262    // Legacy federations are federations that the gateway joined prior to v0.5.0
263    // and do not derive their secrets from the gateway's mnemonic. They also use
264    // a separate database from the gateway's db.
265    pub legacy_federations: Vec<FederationId>,
266}
267
268#[derive(Debug, Serialize, Deserialize, Clone)]
269pub struct PaymentLogPayload {
270    // The position in the log to stop querying. No events will be returned from after
271    // this `EventLogId`. If it is `None`, the last `EventLogId` is used.
272    pub end_position: Option<EventLogId>,
273
274    // The number of events to return
275    pub pagination_size: usize,
276
277    pub federation_id: FederationId,
278    pub event_kinds: Vec<EventKind>,
279}
280
281#[derive(Debug, Serialize, Deserialize, Clone)]
282pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
283
284#[derive(Debug, Serialize, Deserialize, Clone)]
285pub struct PaymentSummaryResponse {
286    pub outgoing: PaymentStats,
287    pub incoming: PaymentStats,
288}
289
290#[derive(Debug, Serialize, Deserialize, Clone)]
291pub struct PaymentStats {
292    pub average_latency: Option<Duration>,
293    pub median_latency: Option<Duration>,
294    pub total_fees: Amount,
295    pub total_success: usize,
296    pub total_failure: usize,
297}
298
299impl PaymentStats {
300    /// Computes the payment statistics for the given structured payment events.
301    pub fn compute(events: &StructuredPaymentEvents) -> Self {
302        PaymentStats {
303            average_latency: get_average(&events.latencies_usecs).map(Duration::from_micros),
304            median_latency: get_median(&events.latencies_usecs).map(Duration::from_micros),
305            total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
306            total_success: events.latencies_usecs.len(),
307            total_failure: events.latencies_failure.len(),
308        }
309    }
310}
311
312#[derive(Debug, Serialize, Deserialize, Clone)]
313pub struct PaymentSummaryPayload {
314    pub start_millis: u64,
315    pub end_millis: u64,
316}
317
318#[derive(Serialize, Deserialize, Debug, Clone)]
319pub struct ChannelInfo {
320    pub remote_pubkey: secp256k1::PublicKey,
321    pub channel_size_sats: u64,
322    pub outbound_liquidity_sats: u64,
323    pub inbound_liquidity_sats: u64,
324    pub is_active: bool,
325    pub funding_outpoint: Option<OutPoint>,
326    pub remote_node_alias: Option<String>,
327    #[serde(default)]
328    pub remote_address: Option<String>,
329}
330
331#[derive(Debug, Serialize, Deserialize, Clone)]
332pub struct OpenChannelRequest {
333    pub pubkey: secp256k1::PublicKey,
334    pub host: String,
335    pub channel_size_sats: u64,
336    pub push_amount_sats: u64,
337}
338
339#[derive(Debug, Serialize, Deserialize, Clone)]
340pub struct SendOnchainRequest {
341    pub address: Address<NetworkUnchecked>,
342    pub amount: BitcoinAmountOrAll,
343    pub fee_rate_sats_per_vbyte: u64,
344}
345
346impl fmt::Display for SendOnchainRequest {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        write!(
349            f,
350            "SendOnchainRequest {{ address: {}, amount: {}, fee_rate_sats_per_vbyte: {} }}",
351            self.address.assume_checked_ref(),
352            self.amount,
353            self.fee_rate_sats_per_vbyte
354        )
355    }
356}
357
358#[derive(Debug, Serialize, Deserialize, Clone)]
359pub struct CloseChannelsWithPeerRequest {
360    pub pubkey: secp256k1::PublicKey,
361    #[serde(default)]
362    pub force: bool,
363    pub sats_per_vbyte: Option<u64>,
364}
365
366impl fmt::Display for CloseChannelsWithPeerRequest {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        write!(
369            f,
370            "CloseChannelsWithPeerRequest {{ pubkey: {}, force: {}, sats_per_vbyte: {} }}",
371            self.pubkey,
372            self.force,
373            match self.sats_per_vbyte {
374                Some(sats) => sats.to_string(),
375                None => "None".to_string(),
376            }
377        )
378    }
379}
380
381#[derive(Debug, Serialize, Deserialize, Clone)]
382pub struct CloseChannelsWithPeerResponse {
383    pub num_channels_closed: u32,
384}
385
386#[derive(Debug, Serialize, Deserialize, Clone)]
387pub struct GetInvoiceRequest {
388    pub payment_hash: sha256::Hash,
389}
390
391#[derive(Debug, Serialize, Deserialize, Clone)]
392pub struct GetInvoiceResponse {
393    pub preimage: Option<String>,
394    pub payment_hash: Option<sha256::Hash>,
395    pub amount: Amount,
396    pub created_at: SystemTime,
397    pub status: PaymentStatus,
398}
399
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub struct ListTransactionsPayload {
402    pub start_secs: u64,
403    pub end_secs: u64,
404}
405
406#[derive(Debug, Serialize, Deserialize, Clone)]
407pub struct ListTransactionsResponse {
408    pub transactions: Vec<PaymentDetails>,
409}
410
411#[derive(Debug, Serialize, Deserialize, Clone)]
412pub struct PaymentDetails {
413    pub payment_hash: Option<sha256::Hash>,
414    pub preimage: Option<String>,
415    pub payment_kind: PaymentKind,
416    pub amount: Amount,
417    pub direction: PaymentDirection,
418    pub status: PaymentStatus,
419    pub timestamp_secs: u64,
420}
421
422#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
423pub enum PaymentKind {
424    Bolt11,
425    Bolt12Offer,
426    Bolt12Refund,
427    Onchain,
428}
429
430#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
431pub enum PaymentDirection {
432    Outbound,
433    Inbound,
434}
435
436#[derive(Debug, Serialize, Deserialize, Clone)]
437pub struct CreateOfferPayload {
438    pub amount: Option<Amount>,
439    pub description: Option<String>,
440    pub expiry_secs: Option<u32>,
441    pub quantity: Option<u64>,
442}
443
444#[derive(Debug, Serialize, Deserialize, Clone)]
445pub struct CreateOfferResponse {
446    pub offer: String,
447}
448
449#[derive(Debug, Serialize, Deserialize, Clone)]
450pub struct PayOfferPayload {
451    pub offer: String,
452    pub amount: Option<Amount>,
453    pub quantity: Option<u64>,
454    pub payer_note: Option<String>,
455}
456
457#[derive(Debug, Serialize, Deserialize, Clone)]
458pub struct PayOfferResponse {
459    pub preimage: String,
460}
461
462#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
463pub enum PaymentStatus {
464    Pending,
465    Succeeded,
466    Failed,
467}
468
469#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
470pub enum LightningMode {
471    #[clap(name = "lnd")]
472    Lnd {
473        /// LND RPC address
474        #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
475        lnd_rpc_addr: String,
476
477        /// LND TLS cert file path
478        #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
479        lnd_tls_cert: String,
480
481        /// LND macaroon file path
482        #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
483        lnd_macaroon: String,
484    },
485    #[clap(name = "ldk")]
486    Ldk {
487        /// LDK lightning server port
488        #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
489        lightning_port: u16,
490
491        /// LDK's Alias
492        #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
493        alias: String,
494    },
495}
496
497#[derive(Clone)]
498pub enum ChainSource {
499    Bitcoind {
500        username: String,
501        password: String,
502        server_url: SafeUrl,
503    },
504    Esplora {
505        server_url: SafeUrl,
506    },
507}
508
509impl fmt::Display for ChainSource {
510    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511        match self {
512            ChainSource::Bitcoind {
513                username: _,
514                password: _,
515                server_url,
516            } => {
517                write!(f, "Bitcoind source with URL: {server_url}")
518            }
519            ChainSource::Esplora { server_url } => {
520                write!(f, "Esplora source with URL: {server_url}")
521            }
522        }
523    }
524}
525
526#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
527#[serde(rename_all = "snake_case")]
528pub enum LightningInfo {
529    Connected {
530        public_key: PublicKey,
531        alias: String,
532        network: Network,
533        block_height: u64,
534        synced_to_chain: bool,
535    },
536    NotConnected,
537}
538
539#[derive(
540    Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Encodable, Decodable, Serialize, Deserialize,
541)]
542#[serde(rename_all = "snake_case")]
543pub enum RegisteredProtocol {
544    Http,
545    Iroh,
546}
547
548#[derive(Debug, Serialize, Deserialize, Clone)]
549pub struct SetMnemonicPayload {
550    pub words: Option<String>,
551}