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::encoding::{Decodable, Encodable};
15use fedimint_core::invite_code::InviteCode;
16use fedimint_core::util::{SafeUrl, get_average, get_median};
17use fedimint_core::{Amount, BitcoinAmountOrAll, secp256k1};
18use fedimint_eventlog::{EventKind, EventLogId, PersistedLogEntry, StructuredPaymentEvents};
19use fedimint_lnv2_common::gateway_api::PaymentFee;
20use fedimint_wallet_client::PegOutFees;
21use lightning_invoice::Bolt11Invoice;
22use serde::{Deserialize, Serialize};
23
24pub mod envs;
25
26pub const V1_API_ENDPOINT: &str = "v1";
27
28pub const ADDRESS_ENDPOINT: &str = "/address";
29pub const ADDRESS_RECHECK_ENDPOINT: &str = "/address_recheck";
30pub const BACKUP_ENDPOINT: &str = "/backup";
31pub const CONFIGURATION_ENDPOINT: &str = "/config";
32pub const CONNECT_FED_ENDPOINT: &str = "/connect_fed";
33pub const CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt11_invoice_for_operator";
34pub const CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT: &str = "/create_bolt12_offer_for_operator";
35pub const GATEWAY_INFO_ENDPOINT: &str = "/info";
36pub const INVITE_CODES_ENDPOINT: &str = "/invite_codes";
37pub const GET_BALANCES_ENDPOINT: &str = "/balances";
38pub const GET_INVOICE_ENDPOINT: &str = "/get_invoice";
39pub const GET_LN_ONCHAIN_ADDRESS_ENDPOINT: &str = "/get_ln_onchain_address";
40pub const LEAVE_FED_ENDPOINT: &str = "/leave_fed";
41pub const LIST_CHANNELS_ENDPOINT: &str = "/list_channels";
42pub const LIST_TRANSACTIONS_ENDPOINT: &str = "/list_transactions";
43pub const MNEMONIC_ENDPOINT: &str = "/mnemonic";
44pub const OPEN_CHANNEL_ENDPOINT: &str = "/open_channel";
45pub const OPEN_CHANNEL_WITH_PUSH_ENDPOINT: &str = "/open_channel_with_push";
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 PEGIN_FROM_ONCHAIN_ENDPOINT: &str = "/pegin_from_onchain";
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";
58pub const WITHDRAW_TO_ONCHAIN_ENDPOINT: &str = "/withdraw_to_onchain";
59
60#[derive(Debug, Serialize, Deserialize, Clone)]
61pub struct ConnectFedPayload {
62 pub invite_code: String,
63 pub use_tor: Option<bool>,
64 pub recover: Option<bool>,
65}
66
67#[derive(Debug, Serialize, Deserialize, Clone)]
68pub struct LeaveFedPayload {
69 pub federation_id: FederationId,
70}
71
72#[derive(Debug, Serialize, Deserialize)]
73pub struct InfoPayload;
74
75#[derive(Debug, Serialize, Deserialize)]
76pub struct BackupPayload {
77 pub federation_id: FederationId,
78}
79
80#[derive(Debug, Serialize, Deserialize, Clone)]
81pub struct ConfigPayload {
82 pub federation_id: Option<FederationId>,
83}
84
85#[derive(Debug, Serialize, Deserialize, Clone)]
86pub struct DepositAddressPayload {
87 pub federation_id: FederationId,
88}
89
90#[derive(Debug, Serialize, Deserialize, Clone)]
91pub struct PeginFromOnchainPayload {
92 pub federation_id: FederationId,
93 pub amount: BitcoinAmountOrAll,
94 pub fee_rate_sats_per_vbyte: u64,
95}
96
97#[derive(Debug, Serialize, Deserialize, Clone)]
98pub struct DepositAddressRecheckPayload {
99 pub address: Address<NetworkUnchecked>,
100 pub federation_id: FederationId,
101}
102
103#[derive(Debug, Serialize, Deserialize, Clone)]
104pub struct WithdrawPayload {
105 pub federation_id: FederationId,
106 pub amount: BitcoinAmountOrAll,
107 pub address: Address<NetworkUnchecked>,
108 #[serde(default)]
111 pub quoted_fees: Option<PegOutFees>,
112}
113
114#[derive(Debug, Serialize, Deserialize, Clone)]
115pub struct WithdrawToOnchainPayload {
116 pub federation_id: FederationId,
117 pub amount: BitcoinAmountOrAll,
118}
119
120#[derive(Debug, Serialize, Deserialize, Clone)]
121pub struct WithdrawResponse {
122 pub txid: bitcoin::Txid,
123 pub fees: PegOutFees,
124}
125
126#[derive(Debug, Serialize, Deserialize, Clone)]
127pub struct WithdrawPreviewPayload {
128 pub federation_id: FederationId,
129 pub amount: BitcoinAmountOrAll,
130 pub address: Address<NetworkUnchecked>,
131}
132
133#[derive(Debug, Serialize, Deserialize, Clone)]
134pub struct WithdrawPreviewResponse {
135 pub withdraw_amount: Amount,
136 pub address: String,
137 pub peg_out_fees: PegOutFees,
138 pub total_cost: Amount,
139 #[serde(default)]
141 pub mint_fees: Option<Amount>,
142}
143
144#[allow(deprecated)]
148#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
149pub enum ConnectorType {
150 Tcp,
151 Tor,
152}
153
154#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
155pub struct FederationConfig {
156 pub invite_code: InviteCode,
157 #[serde(alias = "mint_channel_id")]
160 pub federation_index: u64,
161 pub lightning_fee: PaymentFee,
162 pub transaction_fee: PaymentFee,
163 #[allow(deprecated)] pub _connector: ConnectorType,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
169pub struct FederationInfo {
170 pub federation_id: FederationId,
171 pub federation_name: Option<String>,
172 pub balance_msat: Amount,
173 pub config: FederationConfig,
174 pub last_backup_time: Option<SystemTime>,
175}
176
177#[derive(Debug, Serialize, Deserialize, PartialEq)]
178pub struct GatewayInfo {
179 pub version_hash: String,
180 pub federations: Vec<FederationInfo>,
181 #[serde(alias = "channels")]
184 pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
185 pub gateway_state: String,
186 pub lightning_info: LightningInfo,
187 pub lightning_mode: LightningMode,
188 pub registrations: BTreeMap<RegisteredProtocol, (SafeUrl, secp256k1::PublicKey)>,
189}
190
191#[derive(Debug, Serialize, Deserialize, PartialEq)]
192pub struct GatewayFedConfig {
193 pub federations: BTreeMap<FederationId, JsonClientConfig>,
194}
195
196#[derive(Debug, Serialize, Deserialize, Clone)]
197pub struct SetFeesPayload {
198 pub federation_id: Option<FederationId>,
199 pub lightning_base: Option<Amount>,
200 pub lightning_parts_per_million: Option<u64>,
201 pub transaction_base: Option<Amount>,
202 pub transaction_parts_per_million: Option<u64>,
203}
204
205#[derive(Debug, Serialize, Deserialize, Clone)]
206pub struct CreateInvoiceForOperatorPayload {
207 pub amount_msats: u64,
208 pub expiry_secs: Option<u32>,
209 pub description: Option<String>,
210}
211
212#[derive(Debug, Serialize, Deserialize, Clone)]
213pub struct PayInvoiceForOperatorPayload {
214 pub invoice: Bolt11Invoice,
215}
216
217#[derive(Debug, Serialize, Deserialize, Clone)]
218pub struct SpendEcashPayload {
219 pub federation_id: FederationId,
221 pub amount: Amount,
223}
224
225#[derive(Debug, Serialize, Deserialize, Clone)]
226pub struct SpendEcashResponse {
227 pub notes: String,
229}
230
231#[derive(Debug, Serialize, Deserialize, Clone)]
232pub struct ReceiveEcashPayload {
233 pub notes: String,
235 #[serde(default)]
236 pub wait: bool,
237}
238
239#[derive(Debug, Serialize, Deserialize, Clone)]
240pub struct ReceiveEcashResponse {
241 pub amount: Amount,
242}
243
244#[derive(serde::Serialize, serde::Deserialize, Clone)]
245pub struct GatewayBalances {
246 pub onchain_balance_sats: u64,
247 pub lightning_balance_msats: u64,
248 pub ecash_balances: Vec<FederationBalanceInfo>,
249 pub inbound_lightning_liquidity_msats: u64,
250}
251
252#[derive(serde::Serialize, serde::Deserialize, Clone)]
253pub struct FederationBalanceInfo {
254 pub federation_id: FederationId,
255 pub ecash_balance_msats: Amount,
256}
257
258#[derive(Debug, Serialize, Deserialize, Clone)]
259pub struct MnemonicResponse {
260 pub mnemonic: Vec<String>,
261
262 pub legacy_federations: Vec<FederationId>,
266}
267
268#[derive(Debug, Serialize, Deserialize, Clone)]
269pub struct PaymentLogPayload {
270 pub end_position: Option<EventLogId>,
273
274 pub pagination_size: usize,
276
277 pub federation_id: FederationId,
278
279 pub event_kinds: Vec<EventKind>,
287}
288
289#[derive(Debug, Serialize, Deserialize, Clone)]
290pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
291
292#[derive(Debug, Serialize, Deserialize, Clone)]
293pub struct PaymentSummaryResponse {
294 pub outgoing: PaymentStats,
295 pub incoming: PaymentStats,
296}
297
298#[derive(Debug, Serialize, Deserialize, Clone)]
299pub struct PaymentStats {
300 pub average_latency: Option<Duration>,
301 pub median_latency: Option<Duration>,
302 pub total_fees: Amount,
303 pub total_success: usize,
304 pub total_failure: usize,
305}
306
307impl PaymentStats {
308 pub fn compute(events: &StructuredPaymentEvents) -> Self {
310 PaymentStats {
311 average_latency: get_average(&events.latencies_usecs).map(Duration::from_micros),
312 median_latency: get_median(&events.latencies_usecs).map(Duration::from_micros),
313 total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
314 total_success: events.latencies_usecs.len(),
315 total_failure: events.latencies_failure.len(),
316 }
317 }
318}
319
320#[derive(Debug, Serialize, Deserialize, Clone)]
321pub struct PaymentSummaryPayload {
322 pub start_millis: u64,
323 pub end_millis: u64,
324}
325
326#[derive(Serialize, Deserialize, Debug, Clone)]
327pub struct ChannelInfo {
328 pub remote_pubkey: secp256k1::PublicKey,
329 pub channel_size_sats: u64,
330 pub outbound_liquidity_sats: u64,
331 pub inbound_liquidity_sats: u64,
332 pub is_active: bool,
333 pub funding_outpoint: Option<OutPoint>,
334 pub remote_node_alias: Option<String>,
335 #[serde(default)]
336 pub remote_address: Option<String>,
337}
338
339#[derive(Debug, Serialize, Deserialize, Clone)]
340pub struct OpenChannelRequest {
341 pub pubkey: secp256k1::PublicKey,
342 pub host: String,
343 pub channel_size_sats: u64,
344 pub push_amount_sats: u64,
345}
346
347#[derive(Debug, Serialize, Deserialize, Clone)]
348pub struct SendOnchainRequest {
349 pub address: Address<NetworkUnchecked>,
350 pub amount: BitcoinAmountOrAll,
351 pub fee_rate_sats_per_vbyte: u64,
352}
353
354impl fmt::Display for SendOnchainRequest {
355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 write!(
357 f,
358 "SendOnchainRequest {{ address: {}, amount: {}, fee_rate_sats_per_vbyte: {} }}",
359 self.address.assume_checked_ref(),
360 self.amount,
361 self.fee_rate_sats_per_vbyte
362 )
363 }
364}
365
366#[derive(Debug, Serialize, Deserialize, Clone)]
367pub struct CloseChannelsWithPeerRequest {
368 pub pubkey: secp256k1::PublicKey,
369 #[serde(default)]
370 pub force: bool,
371 pub sats_per_vbyte: Option<u64>,
372}
373
374impl fmt::Display for CloseChannelsWithPeerRequest {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 write!(
377 f,
378 "CloseChannelsWithPeerRequest {{ pubkey: {}, force: {}, sats_per_vbyte: {} }}",
379 self.pubkey,
380 self.force,
381 match self.sats_per_vbyte {
382 Some(sats) => sats.to_string(),
383 None => "None".to_string(),
384 }
385 )
386 }
387}
388
389#[derive(Debug, Serialize, Deserialize, Clone)]
390pub struct CloseChannelsWithPeerResponse {
391 pub num_channels_closed: u32,
392}
393
394#[derive(Debug, Serialize, Deserialize, Clone)]
395pub struct GetInvoiceRequest {
396 pub payment_hash: sha256::Hash,
397}
398
399#[derive(Debug, Serialize, Deserialize, Clone)]
400pub struct GetInvoiceResponse {
401 pub preimage: Option<String>,
402 pub payment_hash: Option<sha256::Hash>,
403 pub amount: Amount,
404 pub created_at: SystemTime,
405 pub status: PaymentStatus,
406}
407
408#[derive(Debug, Serialize, Deserialize, Clone)]
409pub struct ListTransactionsPayload {
410 pub start_secs: u64,
411 pub end_secs: u64,
412}
413
414#[derive(Debug, Serialize, Deserialize, Clone)]
415pub struct ListTransactionsResponse {
416 pub transactions: Vec<PaymentDetails>,
417}
418
419#[derive(Debug, Serialize, Deserialize, Clone)]
420pub struct PaymentDetails {
421 pub payment_hash: Option<sha256::Hash>,
422 pub preimage: Option<String>,
423 pub payment_kind: PaymentKind,
424 pub amount: Amount,
425 pub direction: PaymentDirection,
426 pub status: PaymentStatus,
427 pub timestamp_secs: u64,
428}
429
430#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
431pub enum PaymentKind {
432 Bolt11,
433 Bolt12Offer,
434 Bolt12Refund,
435 Onchain,
436}
437
438#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
439pub enum PaymentDirection {
440 Outbound,
441 Inbound,
442}
443
444#[derive(Debug, Serialize, Deserialize, Clone)]
445pub struct CreateOfferPayload {
446 pub amount: Option<Amount>,
447 pub description: Option<String>,
448 pub expiry_secs: Option<u32>,
449 pub quantity: Option<u64>,
450}
451
452#[derive(Debug, Serialize, Deserialize, Clone)]
453pub struct CreateOfferResponse {
454 pub offer: String,
455}
456
457#[derive(Debug, Serialize, Deserialize, Clone)]
458pub struct PayOfferPayload {
459 pub offer: String,
460 pub amount: Option<Amount>,
461 pub quantity: Option<u64>,
462 pub payer_note: Option<String>,
463}
464
465#[derive(Debug, Serialize, Deserialize, Clone)]
466pub struct PayOfferResponse {
467 pub preimage: String,
468}
469
470#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
471pub enum PaymentStatus {
472 Pending,
473 Succeeded,
474 Failed,
475}
476
477#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
478pub enum LightningMode {
479 #[clap(name = "lnd")]
480 Lnd {
481 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
483 lnd_rpc_addr: String,
484
485 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
487 lnd_tls_cert: String,
488
489 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
491 lnd_macaroon: String,
492 },
493 #[clap(name = "ldk")]
494 Ldk {
495 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
497 lightning_port: u16,
498
499 #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
501 alias: String,
502 },
503}
504
505#[derive(Clone)]
506pub enum ChainSource {
507 Bitcoind {
508 username: String,
509 password: String,
510 server_url: SafeUrl,
511 },
512 Esplora {
513 server_url: SafeUrl,
514 },
515}
516
517impl fmt::Display for ChainSource {
518 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519 match self {
520 ChainSource::Bitcoind {
521 username: _,
522 password: _,
523 server_url,
524 } => {
525 write!(f, "Bitcoind source with URL: {server_url}")
526 }
527 ChainSource::Esplora { server_url } => {
528 write!(f, "Esplora source with URL: {server_url}")
529 }
530 }
531 }
532}
533
534#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
535#[serde(rename_all = "snake_case")]
536pub enum LightningInfo {
537 Connected {
538 public_key: PublicKey,
539 alias: String,
540 network: Network,
541 block_height: u64,
542 synced_to_chain: bool,
543 },
544 NotConnected,
545}
546
547#[derive(
548 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Encodable, Decodable, Serialize, Deserialize,
549)]
550#[serde(rename_all = "snake_case")]
551pub enum RegisteredProtocol {
552 Http,
553 Iroh,
554}
555
556#[derive(Debug, Serialize, Deserialize, Clone)]
557pub struct SetMnemonicPayload {
558 pub words: Option<String>,
559}