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