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::{Address, Network};
8use clap::Subcommand;
9use envs::{
10    FM_LDK_ALIAS_ENV, FM_LND_MACAROON_ENV, FM_LND_RPC_ADDR_ENV, FM_LND_TLS_CERT_ENV, FM_PORT_LDK,
11};
12use fedimint_api_client::api::net::ConnectorType;
13use fedimint_core::config::{FederationId, JsonClientConfig};
14use fedimint_core::core::OperationId;
15use fedimint_core::encoding::{Decodable, Encodable};
16use fedimint_core::invite_code::InviteCode;
17use fedimint_core::util::{SafeUrl, get_average, get_median};
18use fedimint_core::{Amount, BitcoinAmountOrAll, secp256k1};
19use fedimint_eventlog::{EventKind, EventLogId, PersistedLogEntry, StructuredPaymentEvents};
20use fedimint_lnv2_common::gateway_api::PaymentFee;
21use fedimint_mint_client::OOBNotes;
22use fedimint_wallet_client::PegOutFees;
23use lightning_invoice::Bolt11Invoice;
24use serde::{Deserialize, Serialize};
25
26pub mod envs;
27
28pub const FEDIMINT_GATEWAY_ALPN: &[u8] = b"FEDIMINT_GATEWAY_ALPN";
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct IrohGatewayRequest {
32    /// REST API route for specifying which action to take
33    pub route: String,
34
35    /// Parameters for the request
36    pub params: Option<serde_json::Value>,
37
38    /// Password for authenticated requests to the gateway
39    pub password: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct IrohGatewayResponse {
44    pub status: u16,
45    pub body: serde_json::Value,
46}
47
48pub const V1_API_ENDPOINT: &str = "v1";
49
50pub const ADDRESS_ENDPOINT: &str = "/address";
51pub const ADDRESS_RECHECK_ENDPOINT: &str = "/address_recheck";
52pub const BACKUP_ENDPOINT: &str = "/backup";
53pub const CONFIGURATION_ENDPOINT: &str = "/config";
54pub const CONNECT_FED_ENDPOINT: &str = "/connect_fed";
55pub const CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt11_invoice_for_operator";
56pub const CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt12_offer_for_operator";
57pub const GATEWAY_INFO_ENDPOINT: &str = "/info";
58pub const GET_BALANCES_ENDPOINT: &str = "/balances";
59pub const GET_INVOICE_ENDPOINT: &str = "/get_invoice";
60pub const GET_LN_ONCHAIN_ADDRESS_ENDPOINT: &str = "/get_ln_onchain_address";
61pub const LEAVE_FED_ENDPOINT: &str = "/leave_fed";
62pub const LIST_CHANNELS_ENDPOINT: &str = "/list_channels";
63pub const LIST_TRANSACTIONS_ENDPOINT: &str = "/list_transactions";
64pub const MNEMONIC_ENDPOINT: &str = "/mnemonic";
65pub const OPEN_CHANNEL_ENDPOINT: &str = "/open_channel";
66pub const CLOSE_CHANNELS_WITH_PEER_ENDPOINT: &str = "/close_channels_with_peer";
67pub const PAY_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/pay_invoice_for_operator";
68pub const PAY_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/pay_offer_for_operator";
69pub const PAYMENT_LOG_ENDPOINT: &str = "/payment_log";
70pub const PAYMENT_SUMMARY_ENDPOINT: &str = "/payment_summary";
71pub const RECEIVE_ECASH_ENDPOINT: &str = "/receive_ecash";
72pub const SET_FEES_ENDPOINT: &str = "/set_fees";
73pub const STOP_ENDPOINT: &str = "/stop";
74pub const SEND_ONCHAIN_ENDPOINT: &str = "/send_onchain";
75pub const SPEND_ECASH_ENDPOINT: &str = "/spend_ecash";
76pub const WITHDRAW_ENDPOINT: &str = "/withdraw";
77
78#[derive(Debug, Serialize, Deserialize, Clone)]
79pub struct ConnectFedPayload {
80    pub invite_code: String,
81    pub use_tor: Option<bool>,
82    pub recover: Option<bool>,
83}
84
85#[derive(Debug, Serialize, Deserialize, Clone)]
86pub struct LeaveFedPayload {
87    pub federation_id: FederationId,
88}
89
90#[derive(Debug, Serialize, Deserialize)]
91pub struct InfoPayload;
92
93#[derive(Debug, Serialize, Deserialize)]
94pub struct BackupPayload {
95    pub federation_id: FederationId,
96}
97
98#[derive(Debug, Serialize, Deserialize, Clone)]
99pub struct ConfigPayload {
100    pub federation_id: Option<FederationId>,
101}
102
103#[derive(Debug, Serialize, Deserialize, Clone)]
104pub struct DepositAddressPayload {
105    pub federation_id: FederationId,
106}
107
108#[derive(Debug, Serialize, Deserialize, Clone)]
109pub struct DepositAddressRecheckPayload {
110    pub address: Address<NetworkUnchecked>,
111    pub federation_id: FederationId,
112}
113
114#[derive(Debug, Serialize, Deserialize, Clone)]
115pub struct WithdrawPayload {
116    pub federation_id: FederationId,
117    pub amount: BitcoinAmountOrAll,
118    pub address: Address<NetworkUnchecked>,
119}
120
121#[derive(Debug, Serialize, Deserialize, Clone)]
122pub struct WithdrawResponse {
123    pub txid: bitcoin::Txid,
124    pub fees: PegOutFees,
125}
126
127#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
128pub struct FederationConfig {
129    pub invite_code: InviteCode,
130    // Unique integer identifier per-federation that is assigned when the gateways joins a
131    // federation.
132    #[serde(alias = "mint_channel_id")]
133    pub federation_index: u64,
134    pub lightning_fee: PaymentFee,
135    pub transaction_fee: PaymentFee,
136    pub connector: ConnectorType,
137}
138
139/// Information about one of the feds we are connected to
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
141pub struct FederationInfo {
142    pub federation_id: FederationId,
143    pub federation_name: Option<String>,
144    pub balance_msat: Amount,
145    pub config: FederationConfig,
146}
147
148#[derive(Debug, Serialize, Deserialize, PartialEq)]
149pub struct GatewayInfo {
150    pub version_hash: String,
151    pub federations: Vec<FederationInfo>,
152    /// Mapping from short channel id to the federation id that it belongs to.
153    // TODO: Remove this alias once it no longer breaks backwards compatibility.
154    #[serde(alias = "channels")]
155    pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
156    pub lightning_pub_key: Option<String>,
157    pub lightning_alias: Option<String>,
158    pub gateway_id: secp256k1::PublicKey,
159    pub gateway_state: String,
160    pub network: Network,
161    // TODO: This is here to allow for backwards compatibility with old versions of this struct. We
162    // should be able to remove it once 0.4.0 is released.
163    #[serde(default)]
164    pub block_height: Option<u32>,
165    // TODO: This is here to allow for backwards compatibility with old versions of this struct. We
166    // should be able to remove it once 0.4.0 is released.
167    #[serde(default)]
168    pub synced_to_chain: bool,
169    pub api: SafeUrl,
170    pub iroh_api: SafeUrl,
171    pub lightning_mode: LightningMode,
172}
173
174#[derive(Debug, Serialize, Deserialize, PartialEq)]
175pub struct GatewayFedConfig {
176    pub federations: BTreeMap<FederationId, JsonClientConfig>,
177}
178
179#[derive(Debug, Serialize, Deserialize, Clone)]
180pub struct SetFeesPayload {
181    pub federation_id: Option<FederationId>,
182    pub lightning_base: Option<Amount>,
183    pub lightning_parts_per_million: Option<u64>,
184    pub transaction_base: Option<Amount>,
185    pub transaction_parts_per_million: Option<u64>,
186}
187
188#[derive(Debug, Serialize, Deserialize, Clone)]
189pub struct CreateInvoiceForOperatorPayload {
190    pub amount_msats: u64,
191    pub expiry_secs: Option<u32>,
192    pub description: Option<String>,
193}
194
195#[derive(Debug, Serialize, Deserialize, Clone)]
196pub struct PayInvoiceForOperatorPayload {
197    pub invoice: Bolt11Invoice,
198}
199
200#[derive(Debug, Serialize, Deserialize, Clone)]
201pub struct SpendEcashPayload {
202    /// Federation id of the e-cash to spend
203    pub federation_id: FederationId,
204    /// The amount of e-cash to spend
205    pub amount: Amount,
206    /// If the exact amount cannot be represented, return e-cash of a higher
207    /// value instead of failing
208    #[serde(default)]
209    pub allow_overpay: bool,
210    /// After how many seconds we will try to reclaim the e-cash if it
211    /// hasn't been redeemed by the recipient. Defaults to one week.
212    #[serde(default = "default_timeout")]
213    pub timeout: u64,
214    /// If the necessary information to join the federation the e-cash
215    /// belongs to should be included in the serialized notes
216    #[serde(default)]
217    pub include_invite: bool,
218}
219
220/// Default timeout for e-cash redemption of one week in seconds
221fn default_timeout() -> u64 {
222    604_800
223}
224
225#[derive(Debug, Serialize, Deserialize, Clone)]
226pub struct SpendEcashResponse {
227    pub operation_id: OperationId,
228    pub notes: OOBNotes,
229}
230
231#[derive(Debug, Serialize, Deserialize, Clone)]
232pub struct ReceiveEcashPayload {
233    pub notes: OOBNotes,
234    #[serde(default)]
235    pub wait: bool,
236}
237
238#[derive(Debug, Serialize, Deserialize, Clone)]
239pub struct ReceiveEcashResponse {
240    pub amount: Amount,
241}
242
243#[derive(serde::Serialize, serde::Deserialize)]
244pub struct GatewayBalances {
245    pub onchain_balance_sats: u64,
246    pub lightning_balance_msats: u64,
247    pub ecash_balances: Vec<FederationBalanceInfo>,
248    pub inbound_lightning_liquidity_msats: u64,
249}
250
251#[derive(serde::Serialize, serde::Deserialize)]
252pub struct FederationBalanceInfo {
253    pub federation_id: FederationId,
254    pub ecash_balance_msats: Amount,
255}
256
257#[derive(Debug, Serialize, Deserialize, Clone)]
258pub struct MnemonicResponse {
259    pub mnemonic: Vec<String>,
260
261    // Legacy federations are federations that the gateway joined prior to v0.5.0
262    // and do not derive their secrets from the gateway's mnemonic. They also use
263    // a separate database from the gateway's db.
264    pub legacy_federations: Vec<FederationId>,
265}
266
267#[derive(Debug, Serialize, Deserialize, Clone)]
268pub struct PaymentLogPayload {
269    // The position in the log to stop querying. No events will be returned from after
270    // this `EventLogId`. If it is `None`, the last `EventLogId` is used.
271    pub end_position: Option<EventLogId>,
272
273    // The number of events to return
274    pub pagination_size: usize,
275
276    pub federation_id: FederationId,
277    pub event_kinds: Vec<EventKind>,
278}
279
280#[derive(Debug, Serialize, Deserialize, Clone)]
281pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
282
283#[derive(Debug, Serialize, Deserialize, Clone)]
284pub struct PaymentSummaryResponse {
285    pub outgoing: PaymentStats,
286    pub incoming: PaymentStats,
287}
288
289#[derive(Debug, Serialize, Deserialize, Clone)]
290pub struct PaymentStats {
291    pub average_latency: Option<Duration>,
292    pub median_latency: Option<Duration>,
293    pub total_fees: Amount,
294    pub total_success: usize,
295    pub total_failure: usize,
296}
297
298impl PaymentStats {
299    /// Computes the payment statistics for the given structured payment events.
300    pub fn compute(events: &StructuredPaymentEvents) -> Self {
301        PaymentStats {
302            average_latency: get_average(&events.latencies).map(Duration::from_micros),
303            median_latency: get_median(&events.latencies).map(Duration::from_micros),
304            total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
305            total_success: events.latencies.len(),
306            total_failure: events.latencies_failure.len(),
307        }
308    }
309}
310
311#[derive(Debug, Serialize, Deserialize, Clone)]
312pub struct PaymentSummaryPayload {
313    pub start_millis: u64,
314    pub end_millis: u64,
315}
316
317#[derive(Serialize, Deserialize, Debug, Clone)]
318pub struct ChannelInfo {
319    pub remote_pubkey: secp256k1::PublicKey,
320    pub channel_size_sats: u64,
321    pub outbound_liquidity_sats: u64,
322    pub inbound_liquidity_sats: u64,
323    pub is_active: bool,
324}
325
326#[derive(Debug, Serialize, Deserialize, Clone)]
327pub struct OpenChannelRequest {
328    pub pubkey: secp256k1::PublicKey,
329    pub host: String,
330    pub channel_size_sats: u64,
331    pub push_amount_sats: u64,
332}
333
334#[derive(Debug, Serialize, Deserialize, Clone)]
335pub struct SendOnchainRequest {
336    pub address: Address<NetworkUnchecked>,
337    pub amount: BitcoinAmountOrAll,
338    pub fee_rate_sats_per_vbyte: u64,
339}
340
341#[derive(Debug, Serialize, Deserialize, Clone)]
342pub struct CloseChannelsWithPeerRequest {
343    pub pubkey: secp256k1::PublicKey,
344    pub force: bool,
345}
346
347#[derive(Debug, Serialize, Deserialize, Clone)]
348pub struct CloseChannelsWithPeerResponse {
349    pub num_channels_closed: u32,
350}
351
352#[derive(Debug, Serialize, Deserialize, Clone)]
353pub struct GetInvoiceRequest {
354    pub payment_hash: sha256::Hash,
355}
356
357#[derive(Debug, Serialize, Deserialize, Clone)]
358pub struct GetInvoiceResponse {
359    pub preimage: Option<String>,
360    pub payment_hash: Option<sha256::Hash>,
361    pub amount: Amount,
362    pub created_at: SystemTime,
363    pub status: PaymentStatus,
364}
365
366#[derive(Debug, Serialize, Deserialize, Clone)]
367pub struct ListTransactionsPayload {
368    pub start_secs: u64,
369    pub end_secs: u64,
370}
371
372#[derive(Debug, Serialize, Deserialize, Clone)]
373pub struct ListTransactionsResponse {
374    pub transactions: Vec<PaymentDetails>,
375}
376
377#[derive(Debug, Serialize, Deserialize, Clone)]
378pub struct PaymentDetails {
379    pub payment_hash: Option<sha256::Hash>,
380    pub preimage: Option<String>,
381    pub payment_kind: PaymentKind,
382    pub amount: Amount,
383    pub direction: PaymentDirection,
384    pub status: PaymentStatus,
385    pub timestamp_secs: u64,
386}
387
388#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
389pub enum PaymentKind {
390    Bolt11,
391    Bolt12Offer,
392    Bolt12Refund,
393    Onchain,
394}
395
396#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
397pub enum PaymentDirection {
398    Outbound,
399    Inbound,
400}
401
402#[derive(Debug, Serialize, Deserialize, Clone)]
403pub struct CreateOfferPayload {
404    pub amount: Option<Amount>,
405    pub description: Option<String>,
406    pub expiry_secs: Option<u32>,
407    pub quantity: Option<u64>,
408}
409
410#[derive(Debug, Serialize, Deserialize, Clone)]
411pub struct CreateOfferResponse {
412    pub offer: String,
413}
414
415#[derive(Debug, Serialize, Deserialize, Clone)]
416pub struct PayOfferPayload {
417    pub offer: String,
418    pub amount: Option<Amount>,
419    pub quantity: Option<u64>,
420    pub payer_note: Option<String>,
421}
422
423#[derive(Debug, Serialize, Deserialize, Clone)]
424pub struct PayOfferResponse {
425    pub preimage: String,
426}
427
428#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
429pub enum PaymentStatus {
430    Pending,
431    Succeeded,
432    Failed,
433}
434
435#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
436pub enum LightningMode {
437    #[clap(name = "lnd")]
438    Lnd {
439        /// LND RPC address
440        #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
441        lnd_rpc_addr: String,
442
443        /// LND TLS cert file path
444        #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
445        lnd_tls_cert: String,
446
447        /// LND macaroon file path
448        #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
449        lnd_macaroon: String,
450    },
451    #[clap(name = "ldk")]
452    Ldk {
453        /// LDK lightning server port
454        #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
455        lightning_port: u16,
456
457        /// LDK's Alias
458        #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
459        alias: String,
460    },
461}
462
463#[derive(Clone)]
464pub enum ChainSource {
465    Bitcoind {
466        username: String,
467        password: String,
468        server_url: SafeUrl,
469    },
470    Esplora {
471        server_url: SafeUrl,
472    },
473}
474
475impl fmt::Display for ChainSource {
476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477        match self {
478            ChainSource::Bitcoind {
479                username: _,
480                password: _,
481                server_url,
482            } => {
483                write!(f, "Bitcoind source with URL: {server_url}")
484            }
485            ChainSource::Esplora { server_url } => {
486                write!(f, "Esplora source with URL: {server_url}")
487            }
488        }
489    }
490}