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_id: secp256k1::PublicKey,
139 pub gateway_state: String,
140 pub lightning_info: LightningInfo,
141 pub api: SafeUrl,
142 pub iroh_api: SafeUrl,
143 pub lightning_mode: LightningMode,
144}
145
146#[derive(Debug, Serialize, Deserialize, PartialEq)]
147pub struct GatewayFedConfig {
148 pub federations: BTreeMap<FederationId, JsonClientConfig>,
149}
150
151#[derive(Debug, Serialize, Deserialize, Clone)]
152pub struct SetFeesPayload {
153 pub federation_id: Option<FederationId>,
154 pub lightning_base: Option<Amount>,
155 pub lightning_parts_per_million: Option<u64>,
156 pub transaction_base: Option<Amount>,
157 pub transaction_parts_per_million: Option<u64>,
158}
159
160#[derive(Debug, Serialize, Deserialize, Clone)]
161pub struct CreateInvoiceForOperatorPayload {
162 pub amount_msats: u64,
163 pub expiry_secs: Option<u32>,
164 pub description: Option<String>,
165}
166
167#[derive(Debug, Serialize, Deserialize, Clone)]
168pub struct PayInvoiceForOperatorPayload {
169 pub invoice: Bolt11Invoice,
170}
171
172#[derive(Debug, Serialize, Deserialize, Clone)]
173pub struct SpendEcashPayload {
174 pub federation_id: FederationId,
176 pub amount: Amount,
178 #[serde(default)]
181 pub allow_overpay: bool,
182 #[serde(default = "default_timeout")]
185 pub timeout: u64,
186 #[serde(default)]
189 pub include_invite: bool,
190}
191
192fn default_timeout() -> u64 {
194 604_800
195}
196
197#[derive(Debug, Serialize, Deserialize, Clone)]
198pub struct SpendEcashResponse {
199 pub operation_id: OperationId,
200 pub notes: OOBNotes,
201}
202
203#[derive(Debug, Serialize, Deserialize, Clone)]
204pub struct ReceiveEcashPayload {
205 pub notes: OOBNotes,
206 #[serde(default)]
207 pub wait: bool,
208}
209
210#[derive(Debug, Serialize, Deserialize, Clone)]
211pub struct ReceiveEcashResponse {
212 pub amount: Amount,
213}
214
215#[derive(serde::Serialize, serde::Deserialize)]
216pub struct GatewayBalances {
217 pub onchain_balance_sats: u64,
218 pub lightning_balance_msats: u64,
219 pub ecash_balances: Vec<FederationBalanceInfo>,
220 pub inbound_lightning_liquidity_msats: u64,
221}
222
223#[derive(serde::Serialize, serde::Deserialize)]
224pub struct FederationBalanceInfo {
225 pub federation_id: FederationId,
226 pub ecash_balance_msats: Amount,
227}
228
229#[derive(Debug, Serialize, Deserialize, Clone)]
230pub struct MnemonicResponse {
231 pub mnemonic: Vec<String>,
232
233 pub legacy_federations: Vec<FederationId>,
237}
238
239#[derive(Debug, Serialize, Deserialize, Clone)]
240pub struct PaymentLogPayload {
241 pub end_position: Option<EventLogId>,
244
245 pub pagination_size: usize,
247
248 pub federation_id: FederationId,
249 pub event_kinds: Vec<EventKind>,
250}
251
252#[derive(Debug, Serialize, Deserialize, Clone)]
253pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
254
255#[derive(Debug, Serialize, Deserialize, Clone)]
256pub struct PaymentSummaryResponse {
257 pub outgoing: PaymentStats,
258 pub incoming: PaymentStats,
259}
260
261#[derive(Debug, Serialize, Deserialize, Clone)]
262pub struct PaymentStats {
263 pub average_latency: Option<Duration>,
264 pub median_latency: Option<Duration>,
265 pub total_fees: Amount,
266 pub total_success: usize,
267 pub total_failure: usize,
268}
269
270impl PaymentStats {
271 pub fn compute(events: &StructuredPaymentEvents) -> Self {
273 PaymentStats {
274 average_latency: get_average(&events.latencies).map(Duration::from_micros),
275 median_latency: get_median(&events.latencies).map(Duration::from_micros),
276 total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
277 total_success: events.latencies.len(),
278 total_failure: events.latencies_failure.len(),
279 }
280 }
281}
282
283#[derive(Debug, Serialize, Deserialize, Clone)]
284pub struct PaymentSummaryPayload {
285 pub start_millis: u64,
286 pub end_millis: u64,
287}
288
289#[derive(Serialize, Deserialize, Debug, Clone)]
290pub struct ChannelInfo {
291 pub remote_pubkey: secp256k1::PublicKey,
292 pub channel_size_sats: u64,
293 pub outbound_liquidity_sats: u64,
294 pub inbound_liquidity_sats: u64,
295 pub is_active: bool,
296 pub funding_outpoint: Option<OutPoint>,
297}
298
299#[derive(Debug, Serialize, Deserialize, Clone)]
300pub struct OpenChannelRequest {
301 pub pubkey: secp256k1::PublicKey,
302 pub host: String,
303 pub channel_size_sats: u64,
304 pub push_amount_sats: u64,
305}
306
307#[derive(Debug, Serialize, Deserialize, Clone)]
308pub struct SendOnchainRequest {
309 pub address: Address<NetworkUnchecked>,
310 pub amount: BitcoinAmountOrAll,
311 pub fee_rate_sats_per_vbyte: u64,
312}
313
314#[derive(Debug, Serialize, Deserialize, Clone)]
315pub struct CloseChannelsWithPeerRequest {
316 pub pubkey: secp256k1::PublicKey,
317 #[serde(default)]
318 pub force: bool,
319 pub sats_per_vbyte: Option<u64>,
320}
321
322impl fmt::Display for CloseChannelsWithPeerRequest {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 write!(
325 f,
326 "CloseChannelsWithPeerRequest {{ pubkey: {}, force: {}, sats_per_vbyte: {} }}",
327 self.pubkey,
328 self.force,
329 match self.sats_per_vbyte {
330 Some(sats) => sats.to_string(),
331 None => "None".to_string(),
332 }
333 )
334 }
335}
336
337#[derive(Debug, Serialize, Deserialize, Clone)]
338pub struct CloseChannelsWithPeerResponse {
339 pub num_channels_closed: u32,
340}
341
342#[derive(Debug, Serialize, Deserialize, Clone)]
343pub struct GetInvoiceRequest {
344 pub payment_hash: sha256::Hash,
345}
346
347#[derive(Debug, Serialize, Deserialize, Clone)]
348pub struct GetInvoiceResponse {
349 pub preimage: Option<String>,
350 pub payment_hash: Option<sha256::Hash>,
351 pub amount: Amount,
352 pub created_at: SystemTime,
353 pub status: PaymentStatus,
354}
355
356#[derive(Debug, Serialize, Deserialize, Clone)]
357pub struct ListTransactionsPayload {
358 pub start_secs: u64,
359 pub end_secs: u64,
360}
361
362#[derive(Debug, Serialize, Deserialize, Clone)]
363pub struct ListTransactionsResponse {
364 pub transactions: Vec<PaymentDetails>,
365}
366
367#[derive(Debug, Serialize, Deserialize, Clone)]
368pub struct PaymentDetails {
369 pub payment_hash: Option<sha256::Hash>,
370 pub preimage: Option<String>,
371 pub payment_kind: PaymentKind,
372 pub amount: Amount,
373 pub direction: PaymentDirection,
374 pub status: PaymentStatus,
375 pub timestamp_secs: u64,
376}
377
378#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
379pub enum PaymentKind {
380 Bolt11,
381 Bolt12Offer,
382 Bolt12Refund,
383 Onchain,
384}
385
386#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
387pub enum PaymentDirection {
388 Outbound,
389 Inbound,
390}
391
392#[derive(Debug, Serialize, Deserialize, Clone)]
393pub struct CreateOfferPayload {
394 pub amount: Option<Amount>,
395 pub description: Option<String>,
396 pub expiry_secs: Option<u32>,
397 pub quantity: Option<u64>,
398}
399
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub struct CreateOfferResponse {
402 pub offer: String,
403}
404
405#[derive(Debug, Serialize, Deserialize, Clone)]
406pub struct PayOfferPayload {
407 pub offer: String,
408 pub amount: Option<Amount>,
409 pub quantity: Option<u64>,
410 pub payer_note: Option<String>,
411}
412
413#[derive(Debug, Serialize, Deserialize, Clone)]
414pub struct PayOfferResponse {
415 pub preimage: String,
416}
417
418#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
419pub enum PaymentStatus {
420 Pending,
421 Succeeded,
422 Failed,
423}
424
425#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
426pub enum LightningMode {
427 #[clap(name = "lnd")]
428 Lnd {
429 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
431 lnd_rpc_addr: String,
432
433 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
435 lnd_tls_cert: String,
436
437 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
439 lnd_macaroon: String,
440 },
441 #[clap(name = "ldk")]
442 Ldk {
443 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
445 lightning_port: u16,
446
447 #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
449 alias: String,
450 },
451}
452
453#[derive(Clone)]
454pub enum ChainSource {
455 Bitcoind {
456 username: String,
457 password: String,
458 server_url: SafeUrl,
459 },
460 Esplora {
461 server_url: SafeUrl,
462 },
463}
464
465impl fmt::Display for ChainSource {
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 match self {
468 ChainSource::Bitcoind {
469 username: _,
470 password: _,
471 server_url,
472 } => {
473 write!(f, "Bitcoind source with URL: {server_url}")
474 }
475 ChainSource::Esplora { server_url } => {
476 write!(f, "Esplora source with URL: {server_url}")
477 }
478 }
479 }
480}
481
482#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
483#[serde(rename_all = "snake_case")]
484pub enum LightningInfo {
485 Connected {
486 public_key: PublicKey,
487 alias: String,
488 network: Network,
489 block_height: u64,
490 synced_to_chain: bool,
491 },
492 NotConnected,
493}