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