fedimint_gateway_common/
lib.rs

1use std::collections::BTreeMap;
2use std::time::{Duration, SystemTime};
3
4use bitcoin::address::NetworkUnchecked;
5use bitcoin::hashes::sha256;
6use bitcoin::{Address, Network};
7use clap::Subcommand;
8use envs::{
9    FM_LDK_ALIAS_ENV, FM_LDK_BITCOIND_RPC_URL, FM_LDK_ESPLORA_SERVER_URL, FM_LDK_NETWORK,
10    FM_LND_MACAROON_ENV, FM_LND_RPC_ADDR_ENV, FM_LND_TLS_CERT_ENV, FM_PORT_LDK,
11};
12use fedimint_api_client::api::net::Connector;
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
26mod 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 GATEWAY_INFO_POST_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_ACTIVE_CHANNELS_ENDPOINT: &str = "/list_active_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}
101
102#[derive(Debug, Serialize, Deserialize, Clone)]
103pub struct WithdrawResponse {
104    pub txid: bitcoin::Txid,
105    pub fees: PegOutFees,
106}
107
108#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
109pub struct FederationConfig {
110    pub invite_code: InviteCode,
111    // Unique integer identifier per-federation that is assigned when the gateways joins a
112    // federation.
113    #[serde(alias = "mint_channel_id")]
114    pub federation_index: u64,
115    pub lightning_fee: PaymentFee,
116    pub transaction_fee: PaymentFee,
117    pub connector: Connector,
118}
119
120/// Information about one of the feds we are connected to
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
122pub struct FederationInfo {
123    pub federation_id: FederationId,
124    pub federation_name: Option<String>,
125    pub balance_msat: Amount,
126    pub config: FederationConfig,
127}
128
129#[derive(Debug, Serialize, Deserialize, PartialEq)]
130pub struct GatewayInfo {
131    pub version_hash: String,
132    pub federations: Vec<FederationInfo>,
133    /// Mapping from short channel id to the federation id that it belongs to.
134    // TODO: Remove this alias once it no longer breaks backwards compatibility.
135    #[serde(alias = "channels")]
136    pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
137    pub lightning_pub_key: Option<String>,
138    pub lightning_alias: Option<String>,
139    pub gateway_id: secp256k1::PublicKey,
140    pub gateway_state: String,
141    pub network: Network,
142    // TODO: This is here to allow for backwards compatibility with old versions of this struct. We
143    // should be able to remove it once 0.4.0 is released.
144    #[serde(default)]
145    pub block_height: Option<u32>,
146    // TODO: This is here to allow for backwards compatibility with old versions of this struct. We
147    // should be able to remove it once 0.4.0 is released.
148    #[serde(default)]
149    pub synced_to_chain: bool,
150    pub api: SafeUrl,
151    pub lightning_mode: LightningMode,
152}
153
154#[derive(Debug, Serialize, Deserialize, PartialEq)]
155pub struct GatewayFedConfig {
156    pub federations: BTreeMap<FederationId, JsonClientConfig>,
157}
158
159#[derive(Debug, Serialize, Deserialize, Clone)]
160pub struct SetFeesPayload {
161    pub federation_id: Option<FederationId>,
162    pub lightning_base: Option<Amount>,
163    pub lightning_parts_per_million: Option<u64>,
164    pub transaction_base: Option<Amount>,
165    pub transaction_parts_per_million: Option<u64>,
166}
167
168#[derive(Debug, Serialize, Deserialize, Clone)]
169pub struct CreateInvoiceForOperatorPayload {
170    pub amount_msats: u64,
171    pub expiry_secs: Option<u32>,
172    pub description: Option<String>,
173}
174
175#[derive(Debug, Serialize, Deserialize, Clone)]
176pub struct PayInvoiceForOperatorPayload {
177    pub invoice: Bolt11Invoice,
178}
179
180#[derive(Debug, Serialize, Deserialize, Clone)]
181pub struct SpendEcashPayload {
182    /// Federation id of the e-cash to spend
183    pub federation_id: FederationId,
184    /// The amount of e-cash to spend
185    pub amount: Amount,
186    /// If the exact amount cannot be represented, return e-cash of a higher
187    /// value instead of failing
188    #[serde(default)]
189    pub allow_overpay: bool,
190    /// After how many seconds we will try to reclaim the e-cash if it
191    /// hasn't been redeemed by the recipient. Defaults to one week.
192    #[serde(default = "default_timeout")]
193    pub timeout: u64,
194    /// If the necessary information to join the federation the e-cash
195    /// belongs to should be included in the serialized notes
196    #[serde(default)]
197    pub include_invite: bool,
198}
199
200/// Default timeout for e-cash redemption of one week in seconds
201fn default_timeout() -> u64 {
202    604_800
203}
204
205#[derive(Debug, Serialize, Deserialize, Clone)]
206pub struct SpendEcashResponse {
207    pub operation_id: OperationId,
208    pub notes: OOBNotes,
209}
210
211#[derive(Debug, Serialize, Deserialize, Clone)]
212pub struct ReceiveEcashPayload {
213    pub notes: OOBNotes,
214    #[serde(default)]
215    pub wait: bool,
216}
217
218#[derive(Debug, Serialize, Deserialize, Clone)]
219pub struct ReceiveEcashResponse {
220    pub amount: Amount,
221}
222
223#[derive(serde::Serialize, serde::Deserialize)]
224pub struct GatewayBalances {
225    pub onchain_balance_sats: u64,
226    pub lightning_balance_msats: u64,
227    pub ecash_balances: Vec<FederationBalanceInfo>,
228    pub inbound_lightning_liquidity_msats: u64,
229}
230
231#[derive(serde::Serialize, serde::Deserialize)]
232pub struct FederationBalanceInfo {
233    pub federation_id: FederationId,
234    pub ecash_balance_msats: Amount,
235}
236
237#[derive(Debug, Serialize, Deserialize, Clone)]
238pub struct MnemonicResponse {
239    pub mnemonic: Vec<String>,
240
241    // Legacy federations are federations that the gateway joined prior to v0.5.0
242    // and do not derive their secrets from the gateway's mnemonic. They also use
243    // a separate database from the gateway's db.
244    pub legacy_federations: Vec<FederationId>,
245}
246
247#[derive(Debug, Serialize, Deserialize, Clone)]
248pub struct PaymentLogPayload {
249    // The position in the log to stop querying. No events will be returned from after
250    // this `EventLogId`. If it is `None`, the last `EventLogId` is used.
251    pub end_position: Option<EventLogId>,
252
253    // The number of events to return
254    pub pagination_size: usize,
255
256    pub federation_id: FederationId,
257    pub event_kinds: Vec<EventKind>,
258}
259
260#[derive(Debug, Serialize, Deserialize, Clone)]
261pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
262
263#[derive(Debug, Serialize, Deserialize, Clone)]
264pub struct PaymentSummaryResponse {
265    pub outgoing: PaymentStats,
266    pub incoming: PaymentStats,
267}
268
269#[derive(Debug, Serialize, Deserialize, Clone)]
270pub struct PaymentStats {
271    pub average_latency: Option<Duration>,
272    pub median_latency: Option<Duration>,
273    pub total_fees: Amount,
274    pub total_success: usize,
275    pub total_failure: usize,
276}
277
278impl PaymentStats {
279    /// Computes the payment statistics for the given structured payment events.
280    pub fn compute(events: &StructuredPaymentEvents) -> Self {
281        PaymentStats {
282            average_latency: get_average(&events.latencies).map(Duration::from_micros),
283            median_latency: get_median(&events.latencies).map(Duration::from_micros),
284            total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
285            total_success: events.latencies.len(),
286            total_failure: events.latencies_failure.len(),
287        }
288    }
289}
290
291#[derive(Debug, Serialize, Deserialize, Clone)]
292pub struct PaymentSummaryPayload {
293    pub start_millis: u64,
294    pub end_millis: u64,
295}
296
297#[derive(Serialize, Deserialize, Debug, Clone)]
298pub struct ChannelInfo {
299    pub remote_pubkey: secp256k1::PublicKey,
300    pub channel_size_sats: u64,
301    pub outbound_liquidity_sats: u64,
302    pub inbound_liquidity_sats: u64,
303    pub short_channel_id: u64,
304}
305
306#[derive(Debug, Serialize, Deserialize, Clone)]
307pub struct OpenChannelRequest {
308    pub pubkey: secp256k1::PublicKey,
309    pub host: String,
310    pub channel_size_sats: u64,
311    pub push_amount_sats: u64,
312}
313
314#[derive(Debug, Serialize, Deserialize, Clone)]
315pub struct SendOnchainRequest {
316    pub address: Address<NetworkUnchecked>,
317    pub amount: BitcoinAmountOrAll,
318    pub fee_rate_sats_per_vbyte: u64,
319}
320
321#[derive(Debug, Serialize, Deserialize, Clone)]
322pub struct CloseChannelsWithPeerRequest {
323    pub pubkey: secp256k1::PublicKey,
324}
325
326#[derive(Debug, Serialize, Deserialize, Clone)]
327pub struct CloseChannelsWithPeerResponse {
328    pub num_channels_closed: u32,
329}
330
331#[derive(Debug, Serialize, Deserialize, Clone)]
332pub struct GetInvoiceRequest {
333    pub payment_hash: sha256::Hash,
334}
335
336#[derive(Debug, Serialize, Deserialize, Clone)]
337pub struct GetInvoiceResponse {
338    pub preimage: Option<String>,
339    pub payment_hash: Option<sha256::Hash>,
340    pub amount: Amount,
341    pub created_at: SystemTime,
342    pub status: PaymentStatus,
343}
344
345#[derive(Debug, Serialize, Deserialize, Clone)]
346pub struct ListTransactionsPayload {
347    pub start_secs: u64,
348    pub end_secs: u64,
349}
350
351#[derive(Debug, Serialize, Deserialize, Clone)]
352pub struct ListTransactionsResponse {
353    pub transactions: Vec<PaymentDetails>,
354}
355
356#[derive(Debug, Serialize, Deserialize, Clone)]
357pub struct PaymentDetails {
358    pub payment_hash: Option<sha256::Hash>,
359    pub preimage: Option<String>,
360    pub payment_kind: PaymentKind,
361    pub amount: Amount,
362    pub direction: PaymentDirection,
363    pub status: PaymentStatus,
364    pub timestamp_secs: u64,
365}
366
367#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
368pub enum PaymentKind {
369    Bolt11,
370    Bolt12Offer,
371    Bolt12Refund,
372    Onchain,
373}
374
375#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
376pub enum PaymentDirection {
377    Outbound,
378    Inbound,
379}
380
381#[derive(Debug, Serialize, Deserialize, Clone)]
382pub struct CreateOfferPayload {
383    pub amount: Option<Amount>,
384    pub description: Option<String>,
385    pub expiry_secs: Option<u32>,
386    pub quantity: Option<u64>,
387}
388
389#[derive(Debug, Serialize, Deserialize, Clone)]
390pub struct CreateOfferResponse {
391    pub offer: String,
392}
393
394#[derive(Debug, Serialize, Deserialize, Clone)]
395pub struct PayOfferPayload {
396    pub offer: String,
397    pub amount: Option<Amount>,
398    pub quantity: Option<u64>,
399    pub payer_note: Option<String>,
400}
401
402#[derive(Debug, Serialize, Deserialize, Clone)]
403pub struct PayOfferResponse {
404    pub preimage: String,
405}
406
407#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
408pub enum PaymentStatus {
409    Pending,
410    Succeeded,
411    Failed,
412}
413
414#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
415pub enum LightningMode {
416    #[clap(name = "lnd")]
417    Lnd {
418        /// LND RPC address
419        #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
420        lnd_rpc_addr: String,
421
422        /// LND TLS cert file path
423        #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
424        lnd_tls_cert: String,
425
426        /// LND macaroon file path
427        #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
428        lnd_macaroon: String,
429    },
430    #[clap(name = "ldk")]
431    Ldk {
432        /// LDK esplora server URL
433        #[arg(long = "ldk-esplora-server-url", env = FM_LDK_ESPLORA_SERVER_URL)]
434        esplora_server_url: Option<String>,
435
436        /// LDK bitcoind server URL
437        #[arg(long = "ldk-bitcoind-rpc-url", env = FM_LDK_BITCOIND_RPC_URL)]
438        bitcoind_rpc_url: Option<String>,
439
440        /// LDK network (defaults to regtest if not provided)
441        #[arg(long = "ldk-network", env = FM_LDK_NETWORK, default_value = "regtest")]
442        network: Network,
443
444        /// LDK lightning server port
445        #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
446        lightning_port: u16,
447
448        /// LDK's Alias
449        #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
450        alias: String,
451    },
452}