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 pub route: String,
34
35 pub params: Option<serde_json::Value>,
37
38 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 #[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#[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 #[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 #[serde(default)]
164 pub block_height: Option<u32>,
165 #[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 pub federation_id: FederationId,
204 pub amount: Amount,
206 #[serde(default)]
209 pub allow_overpay: bool,
210 #[serde(default = "default_timeout")]
213 pub timeout: u64,
214 #[serde(default)]
217 pub include_invite: bool,
218}
219
220fn 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 pub legacy_federations: Vec<FederationId>,
265}
266
267#[derive(Debug, Serialize, Deserialize, Clone)]
268pub struct PaymentLogPayload {
269 pub end_position: Option<EventLogId>,
272
273 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 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 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
441 lnd_rpc_addr: String,
442
443 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
445 lnd_tls_cert: String,
446
447 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
449 lnd_macaroon: String,
450 },
451 #[clap(name = "ldk")]
452 Ldk {
453 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
455 lightning_port: u16,
456
457 #[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}