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::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_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: Connector,
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}
128
129#[derive(Debug, Serialize, Deserialize, PartialEq)]
130pub struct GatewayInfo {
131 pub version_hash: String,
132 pub federations: Vec<FederationInfo>,
133 #[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 #[serde(default)]
145 pub block_height: Option<u32>,
146 #[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 pub federation_id: FederationId,
184 pub amount: Amount,
186 #[serde(default)]
189 pub allow_overpay: bool,
190 #[serde(default = "default_timeout")]
193 pub timeout: u64,
194 #[serde(default)]
197 pub include_invite: bool,
198}
199
200fn 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 pub legacy_federations: Vec<FederationId>,
245}
246
247#[derive(Debug, Serialize, Deserialize, Clone)]
248pub struct PaymentLogPayload {
249 pub end_position: Option<EventLogId>,
252
253 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 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 is_active: bool,
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 pub force: bool,
325}
326
327#[derive(Debug, Serialize, Deserialize, Clone)]
328pub struct CloseChannelsWithPeerResponse {
329 pub num_channels_closed: u32,
330}
331
332#[derive(Debug, Serialize, Deserialize, Clone)]
333pub struct GetInvoiceRequest {
334 pub payment_hash: sha256::Hash,
335}
336
337#[derive(Debug, Serialize, Deserialize, Clone)]
338pub struct GetInvoiceResponse {
339 pub preimage: Option<String>,
340 pub payment_hash: Option<sha256::Hash>,
341 pub amount: Amount,
342 pub created_at: SystemTime,
343 pub status: PaymentStatus,
344}
345
346#[derive(Debug, Serialize, Deserialize, Clone)]
347pub struct ListTransactionsPayload {
348 pub start_secs: u64,
349 pub end_secs: u64,
350}
351
352#[derive(Debug, Serialize, Deserialize, Clone)]
353pub struct ListTransactionsResponse {
354 pub transactions: Vec<PaymentDetails>,
355}
356
357#[derive(Debug, Serialize, Deserialize, Clone)]
358pub struct PaymentDetails {
359 pub payment_hash: Option<sha256::Hash>,
360 pub preimage: Option<String>,
361 pub payment_kind: PaymentKind,
362 pub amount: Amount,
363 pub direction: PaymentDirection,
364 pub status: PaymentStatus,
365 pub timestamp_secs: u64,
366}
367
368#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
369pub enum PaymentKind {
370 Bolt11,
371 Bolt12Offer,
372 Bolt12Refund,
373 Onchain,
374}
375
376#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
377pub enum PaymentDirection {
378 Outbound,
379 Inbound,
380}
381
382#[derive(Debug, Serialize, Deserialize, Clone)]
383pub struct CreateOfferPayload {
384 pub amount: Option<Amount>,
385 pub description: Option<String>,
386 pub expiry_secs: Option<u32>,
387 pub quantity: Option<u64>,
388}
389
390#[derive(Debug, Serialize, Deserialize, Clone)]
391pub struct CreateOfferResponse {
392 pub offer: String,
393}
394
395#[derive(Debug, Serialize, Deserialize, Clone)]
396pub struct PayOfferPayload {
397 pub offer: String,
398 pub amount: Option<Amount>,
399 pub quantity: Option<u64>,
400 pub payer_note: Option<String>,
401}
402
403#[derive(Debug, Serialize, Deserialize, Clone)]
404pub struct PayOfferResponse {
405 pub preimage: String,
406}
407
408#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
409pub enum PaymentStatus {
410 Pending,
411 Succeeded,
412 Failed,
413}
414
415#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
416pub enum LightningMode {
417 #[clap(name = "lnd")]
418 Lnd {
419 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
421 lnd_rpc_addr: String,
422
423 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
425 lnd_tls_cert: String,
426
427 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
429 lnd_macaroon: String,
430 },
431 #[clap(name = "ldk")]
432 Ldk {
433 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
435 lightning_port: u16,
436
437 #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
439 alias: String,
440 },
441}
442
443#[derive(Clone)]
444pub enum ChainSource {
445 Bitcoind {
446 username: String,
447 password: String,
448 server_url: SafeUrl,
449 },
450 Esplora {
451 server_url: SafeUrl,
452 },
453}
454
455impl fmt::Display for ChainSource {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 match self {
458 ChainSource::Bitcoind {
459 username: _,
460 password: _,
461 server_url,
462 } => {
463 write!(f, "Bitcoind source with URL: {server_url}")
464 }
465 ChainSource::Esplora { server_url } => {
466 write!(f, "Esplora source with URL: {server_url}")
467 }
468 }
469 }
470}