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}
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 #[serde(alias = "mint_channel_id")]
114 pub federation_index: u64,
115 pub lightning_fee: PaymentFee,
116 pub transaction_fee: PaymentFee,
117 pub connector: ConnectorType,
118}
119
120#[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 pub last_backup_time: Option<SystemTime>,
128}
129
130#[derive(Debug, Serialize, Deserialize, PartialEq)]
131pub struct GatewayInfo {
132 pub version_hash: String,
133 pub federations: Vec<FederationInfo>,
134 #[serde(alias = "channels")]
137 pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
138 pub gateway_state: String,
139 pub lightning_info: LightningInfo,
140 pub lightning_mode: LightningMode,
141 pub registrations: BTreeMap<RegisteredProtocol, (SafeUrl, secp256k1::PublicKey)>,
142}
143
144#[derive(Debug, Serialize, Deserialize, PartialEq)]
145pub struct GatewayFedConfig {
146 pub federations: BTreeMap<FederationId, JsonClientConfig>,
147}
148
149#[derive(Debug, Serialize, Deserialize, Clone)]
150pub struct SetFeesPayload {
151 pub federation_id: Option<FederationId>,
152 pub lightning_base: Option<Amount>,
153 pub lightning_parts_per_million: Option<u64>,
154 pub transaction_base: Option<Amount>,
155 pub transaction_parts_per_million: Option<u64>,
156}
157
158#[derive(Debug, Serialize, Deserialize, Clone)]
159pub struct CreateInvoiceForOperatorPayload {
160 pub amount_msats: u64,
161 pub expiry_secs: Option<u32>,
162 pub description: Option<String>,
163}
164
165#[derive(Debug, Serialize, Deserialize, Clone)]
166pub struct PayInvoiceForOperatorPayload {
167 pub invoice: Bolt11Invoice,
168}
169
170#[derive(Debug, Serialize, Deserialize, Clone)]
171pub struct SpendEcashPayload {
172 pub federation_id: FederationId,
174 pub amount: Amount,
176 #[serde(default)]
179 pub allow_overpay: bool,
180 #[serde(default = "default_timeout")]
183 pub timeout: u64,
184 #[serde(default)]
187 pub include_invite: bool,
188}
189
190fn default_timeout() -> u64 {
192 604_800
193}
194
195#[derive(Debug, Serialize, Deserialize, Clone)]
196pub struct SpendEcashResponse {
197 pub operation_id: OperationId,
198 pub notes: OOBNotes,
199}
200
201#[derive(Debug, Serialize, Deserialize, Clone)]
202pub struct ReceiveEcashPayload {
203 pub notes: OOBNotes,
204 #[serde(default)]
205 pub wait: bool,
206}
207
208#[derive(Debug, Serialize, Deserialize, Clone)]
209pub struct ReceiveEcashResponse {
210 pub amount: Amount,
211}
212
213#[derive(serde::Serialize, serde::Deserialize, Clone)]
214pub struct GatewayBalances {
215 pub onchain_balance_sats: u64,
216 pub lightning_balance_msats: u64,
217 pub ecash_balances: Vec<FederationBalanceInfo>,
218 pub inbound_lightning_liquidity_msats: u64,
219}
220
221#[derive(serde::Serialize, serde::Deserialize, Clone)]
222pub struct FederationBalanceInfo {
223 pub federation_id: FederationId,
224 pub ecash_balance_msats: Amount,
225}
226
227#[derive(Debug, Serialize, Deserialize, Clone)]
228pub struct MnemonicResponse {
229 pub mnemonic: Vec<String>,
230
231 pub legacy_federations: Vec<FederationId>,
235}
236
237#[derive(Debug, Serialize, Deserialize, Clone)]
238pub struct PaymentLogPayload {
239 pub end_position: Option<EventLogId>,
242
243 pub pagination_size: usize,
245
246 pub federation_id: FederationId,
247 pub event_kinds: Vec<EventKind>,
248}
249
250#[derive(Debug, Serialize, Deserialize, Clone)]
251pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
252
253#[derive(Debug, Serialize, Deserialize, Clone)]
254pub struct PaymentSummaryResponse {
255 pub outgoing: PaymentStats,
256 pub incoming: PaymentStats,
257}
258
259#[derive(Debug, Serialize, Deserialize, Clone)]
260pub struct PaymentStats {
261 pub average_latency: Option<Duration>,
262 pub median_latency: Option<Duration>,
263 pub total_fees: Amount,
264 pub total_success: usize,
265 pub total_failure: usize,
266}
267
268impl PaymentStats {
269 pub fn compute(events: &StructuredPaymentEvents) -> Self {
271 PaymentStats {
272 average_latency: get_average(&events.latencies_usecs).map(Duration::from_micros),
273 median_latency: get_median(&events.latencies_usecs).map(Duration::from_micros),
274 total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
275 total_success: events.latencies_usecs.len(),
276 total_failure: events.latencies_failure.len(),
277 }
278 }
279}
280
281#[derive(Debug, Serialize, Deserialize, Clone)]
282pub struct PaymentSummaryPayload {
283 pub start_millis: u64,
284 pub end_millis: u64,
285}
286
287#[derive(Serialize, Deserialize, Debug, Clone)]
288pub struct ChannelInfo {
289 pub remote_pubkey: secp256k1::PublicKey,
290 pub channel_size_sats: u64,
291 pub outbound_liquidity_sats: u64,
292 pub inbound_liquidity_sats: u64,
293 pub is_active: bool,
294 pub funding_outpoint: Option<OutPoint>,
295}
296
297#[derive(Debug, Serialize, Deserialize, Clone)]
298pub struct OpenChannelRequest {
299 pub pubkey: secp256k1::PublicKey,
300 pub host: String,
301 pub channel_size_sats: u64,
302 pub push_amount_sats: u64,
303}
304
305#[derive(Debug, Serialize, Deserialize, Clone)]
306pub struct SendOnchainRequest {
307 pub address: Address<NetworkUnchecked>,
308 pub amount: BitcoinAmountOrAll,
309 pub fee_rate_sats_per_vbyte: u64,
310}
311
312impl fmt::Display for SendOnchainRequest {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 write!(
315 f,
316 "SendOnchainRequest {{ address: {}, amount: {}, fee_rate_sats_per_vbyte: {} }}",
317 self.address.assume_checked_ref(),
318 self.amount,
319 self.fee_rate_sats_per_vbyte
320 )
321 }
322}
323
324#[derive(Debug, Serialize, Deserialize, Clone)]
325pub struct CloseChannelsWithPeerRequest {
326 pub pubkey: secp256k1::PublicKey,
327 #[serde(default)]
328 pub force: bool,
329 pub sats_per_vbyte: Option<u64>,
330}
331
332impl fmt::Display for CloseChannelsWithPeerRequest {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 write!(
335 f,
336 "CloseChannelsWithPeerRequest {{ pubkey: {}, force: {}, sats_per_vbyte: {} }}",
337 self.pubkey,
338 self.force,
339 match self.sats_per_vbyte {
340 Some(sats) => sats.to_string(),
341 None => "None".to_string(),
342 }
343 )
344 }
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}
491
492#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
493#[serde(rename_all = "snake_case")]
494pub enum LightningInfo {
495 Connected {
496 public_key: PublicKey,
497 alias: String,
498 network: Network,
499 block_height: u64,
500 synced_to_chain: bool,
501 },
502 NotConnected,
503}
504
505#[derive(
506 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Encodable, Decodable, Serialize, Deserialize,
507)]
508#[serde(rename_all = "snake_case")]
509pub enum RegisteredProtocol {
510 Http,
511 Iroh,
512}