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::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 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 INVITE_CODES_ENDPOINT: &str = "/invite_codes";
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 #[serde(default)]
103 pub quoted_fees: Option<PegOutFees>,
104}
105
106#[derive(Debug, Serialize, Deserialize, Clone)]
107pub struct WithdrawResponse {
108 pub txid: bitcoin::Txid,
109 pub fees: PegOutFees,
110}
111
112#[derive(Debug, Serialize, Deserialize, Clone)]
113pub struct WithdrawPreviewPayload {
114 pub federation_id: FederationId,
115 pub amount: BitcoinAmountOrAll,
116 pub address: Address<NetworkUnchecked>,
117}
118
119#[derive(Debug, Serialize, Deserialize, Clone)]
120pub struct WithdrawPreviewResponse {
121 pub withdraw_amount: Amount,
122 pub address: String,
123 pub peg_out_fees: PegOutFees,
124 pub total_cost: Amount,
125 #[serde(default)]
127 pub mint_fees: Option<Amount>,
128}
129
130#[allow(deprecated)]
134#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
135pub enum ConnectorType {
136 Tcp,
137 Tor,
138}
139
140#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
141pub struct FederationConfig {
142 pub invite_code: InviteCode,
143 #[serde(alias = "mint_channel_id")]
146 pub federation_index: u64,
147 pub lightning_fee: PaymentFee,
148 pub transaction_fee: PaymentFee,
149 #[allow(deprecated)] pub _connector: ConnectorType,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
155pub struct FederationInfo {
156 pub federation_id: FederationId,
157 pub federation_name: Option<String>,
158 pub balance_msat: Amount,
159 pub config: FederationConfig,
160 pub last_backup_time: Option<SystemTime>,
161}
162
163#[derive(Debug, Serialize, Deserialize, PartialEq)]
164pub struct GatewayInfo {
165 pub version_hash: String,
166 pub federations: Vec<FederationInfo>,
167 #[serde(alias = "channels")]
170 pub federation_fake_scids: Option<BTreeMap<u64, FederationId>>,
171 pub gateway_state: String,
172 pub lightning_info: LightningInfo,
173 pub lightning_mode: LightningMode,
174 pub registrations: BTreeMap<RegisteredProtocol, (SafeUrl, secp256k1::PublicKey)>,
175}
176
177#[derive(Debug, Serialize, Deserialize, PartialEq)]
178pub struct GatewayFedConfig {
179 pub federations: BTreeMap<FederationId, JsonClientConfig>,
180}
181
182#[derive(Debug, Serialize, Deserialize, Clone)]
183pub struct SetFeesPayload {
184 pub federation_id: Option<FederationId>,
185 pub lightning_base: Option<Amount>,
186 pub lightning_parts_per_million: Option<u64>,
187 pub transaction_base: Option<Amount>,
188 pub transaction_parts_per_million: Option<u64>,
189}
190
191#[derive(Debug, Serialize, Deserialize, Clone)]
192pub struct CreateInvoiceForOperatorPayload {
193 pub amount_msats: u64,
194 pub expiry_secs: Option<u32>,
195 pub description: Option<String>,
196}
197
198#[derive(Debug, Serialize, Deserialize, Clone)]
199pub struct PayInvoiceForOperatorPayload {
200 pub invoice: Bolt11Invoice,
201}
202
203#[derive(Debug, Serialize, Deserialize, Clone)]
204pub struct SpendEcashPayload {
205 pub federation_id: FederationId,
207 pub amount: Amount,
209 #[serde(default)]
212 pub allow_overpay: bool,
213 #[serde(default = "default_timeout")]
216 pub timeout: u64,
217 #[serde(default)]
220 pub include_invite: bool,
221}
222
223fn default_timeout() -> u64 {
225 604_800
226}
227
228#[derive(Debug, Serialize, Deserialize, Clone)]
229pub struct SpendEcashResponse {
230 pub operation_id: OperationId,
231 pub notes: OOBNotes,
232}
233
234#[derive(Debug, Serialize, Deserialize, Clone)]
235pub struct ReceiveEcashPayload {
236 pub notes: OOBNotes,
237 #[serde(default)]
238 pub wait: bool,
239}
240
241#[derive(Debug, Serialize, Deserialize, Clone)]
242pub struct ReceiveEcashResponse {
243 pub amount: Amount,
244}
245
246#[derive(serde::Serialize, serde::Deserialize, Clone)]
247pub struct GatewayBalances {
248 pub onchain_balance_sats: u64,
249 pub lightning_balance_msats: u64,
250 pub ecash_balances: Vec<FederationBalanceInfo>,
251 pub inbound_lightning_liquidity_msats: u64,
252}
253
254#[derive(serde::Serialize, serde::Deserialize, Clone)]
255pub struct FederationBalanceInfo {
256 pub federation_id: FederationId,
257 pub ecash_balance_msats: Amount,
258}
259
260#[derive(Debug, Serialize, Deserialize, Clone)]
261pub struct MnemonicResponse {
262 pub mnemonic: Vec<String>,
263
264 pub legacy_federations: Vec<FederationId>,
268}
269
270#[derive(Debug, Serialize, Deserialize, Clone)]
271pub struct PaymentLogPayload {
272 pub end_position: Option<EventLogId>,
275
276 pub pagination_size: usize,
278
279 pub federation_id: FederationId,
280 pub event_kinds: Vec<EventKind>,
281}
282
283#[derive(Debug, Serialize, Deserialize, Clone)]
284pub struct PaymentLogResponse(pub Vec<PersistedLogEntry>);
285
286#[derive(Debug, Serialize, Deserialize, Clone)]
287pub struct PaymentSummaryResponse {
288 pub outgoing: PaymentStats,
289 pub incoming: PaymentStats,
290}
291
292#[derive(Debug, Serialize, Deserialize, Clone)]
293pub struct PaymentStats {
294 pub average_latency: Option<Duration>,
295 pub median_latency: Option<Duration>,
296 pub total_fees: Amount,
297 pub total_success: usize,
298 pub total_failure: usize,
299}
300
301impl PaymentStats {
302 pub fn compute(events: &StructuredPaymentEvents) -> Self {
304 PaymentStats {
305 average_latency: get_average(&events.latencies_usecs).map(Duration::from_micros),
306 median_latency: get_median(&events.latencies_usecs).map(Duration::from_micros),
307 total_fees: Amount::from_msats(events.fees.iter().map(|a| a.msats).sum()),
308 total_success: events.latencies_usecs.len(),
309 total_failure: events.latencies_failure.len(),
310 }
311 }
312}
313
314#[derive(Debug, Serialize, Deserialize, Clone)]
315pub struct PaymentSummaryPayload {
316 pub start_millis: u64,
317 pub end_millis: u64,
318}
319
320#[derive(Serialize, Deserialize, Debug, Clone)]
321pub struct ChannelInfo {
322 pub remote_pubkey: secp256k1::PublicKey,
323 pub channel_size_sats: u64,
324 pub outbound_liquidity_sats: u64,
325 pub inbound_liquidity_sats: u64,
326 pub is_active: bool,
327 pub funding_outpoint: Option<OutPoint>,
328 pub remote_node_alias: Option<String>,
329}
330
331#[derive(Debug, Serialize, Deserialize, Clone)]
332pub struct OpenChannelRequest {
333 pub pubkey: secp256k1::PublicKey,
334 pub host: String,
335 pub channel_size_sats: u64,
336 pub push_amount_sats: u64,
337}
338
339#[derive(Debug, Serialize, Deserialize, Clone)]
340pub struct SendOnchainRequest {
341 pub address: Address<NetworkUnchecked>,
342 pub amount: BitcoinAmountOrAll,
343 pub fee_rate_sats_per_vbyte: u64,
344}
345
346impl fmt::Display for SendOnchainRequest {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 write!(
349 f,
350 "SendOnchainRequest {{ address: {}, amount: {}, fee_rate_sats_per_vbyte: {} }}",
351 self.address.assume_checked_ref(),
352 self.amount,
353 self.fee_rate_sats_per_vbyte
354 )
355 }
356}
357
358#[derive(Debug, Serialize, Deserialize, Clone)]
359pub struct CloseChannelsWithPeerRequest {
360 pub pubkey: secp256k1::PublicKey,
361 #[serde(default)]
362 pub force: bool,
363 pub sats_per_vbyte: Option<u64>,
364}
365
366impl fmt::Display for CloseChannelsWithPeerRequest {
367 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368 write!(
369 f,
370 "CloseChannelsWithPeerRequest {{ pubkey: {}, force: {}, sats_per_vbyte: {} }}",
371 self.pubkey,
372 self.force,
373 match self.sats_per_vbyte {
374 Some(sats) => sats.to_string(),
375 None => "None".to_string(),
376 }
377 )
378 }
379}
380
381#[derive(Debug, Serialize, Deserialize, Clone)]
382pub struct CloseChannelsWithPeerResponse {
383 pub num_channels_closed: u32,
384}
385
386#[derive(Debug, Serialize, Deserialize, Clone)]
387pub struct GetInvoiceRequest {
388 pub payment_hash: sha256::Hash,
389}
390
391#[derive(Debug, Serialize, Deserialize, Clone)]
392pub struct GetInvoiceResponse {
393 pub preimage: Option<String>,
394 pub payment_hash: Option<sha256::Hash>,
395 pub amount: Amount,
396 pub created_at: SystemTime,
397 pub status: PaymentStatus,
398}
399
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub struct ListTransactionsPayload {
402 pub start_secs: u64,
403 pub end_secs: u64,
404}
405
406#[derive(Debug, Serialize, Deserialize, Clone)]
407pub struct ListTransactionsResponse {
408 pub transactions: Vec<PaymentDetails>,
409}
410
411#[derive(Debug, Serialize, Deserialize, Clone)]
412pub struct PaymentDetails {
413 pub payment_hash: Option<sha256::Hash>,
414 pub preimage: Option<String>,
415 pub payment_kind: PaymentKind,
416 pub amount: Amount,
417 pub direction: PaymentDirection,
418 pub status: PaymentStatus,
419 pub timestamp_secs: u64,
420}
421
422#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
423pub enum PaymentKind {
424 Bolt11,
425 Bolt12Offer,
426 Bolt12Refund,
427 Onchain,
428}
429
430#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
431pub enum PaymentDirection {
432 Outbound,
433 Inbound,
434}
435
436#[derive(Debug, Serialize, Deserialize, Clone)]
437pub struct CreateOfferPayload {
438 pub amount: Option<Amount>,
439 pub description: Option<String>,
440 pub expiry_secs: Option<u32>,
441 pub quantity: Option<u64>,
442}
443
444#[derive(Debug, Serialize, Deserialize, Clone)]
445pub struct CreateOfferResponse {
446 pub offer: String,
447}
448
449#[derive(Debug, Serialize, Deserialize, Clone)]
450pub struct PayOfferPayload {
451 pub offer: String,
452 pub amount: Option<Amount>,
453 pub quantity: Option<u64>,
454 pub payer_note: Option<String>,
455}
456
457#[derive(Debug, Serialize, Deserialize, Clone)]
458pub struct PayOfferResponse {
459 pub preimage: String,
460}
461
462#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
463pub enum PaymentStatus {
464 Pending,
465 Succeeded,
466 Failed,
467}
468
469#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
470pub enum LightningMode {
471 #[clap(name = "lnd")]
472 Lnd {
473 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
475 lnd_rpc_addr: String,
476
477 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
479 lnd_tls_cert: String,
480
481 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
483 lnd_macaroon: String,
484 },
485 #[clap(name = "ldk")]
486 Ldk {
487 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
489 lightning_port: u16,
490
491 #[arg(long = "ldk-alias", env = FM_LDK_ALIAS_ENV)]
493 alias: String,
494 },
495}
496
497#[derive(Clone)]
498pub enum ChainSource {
499 Bitcoind {
500 username: String,
501 password: String,
502 server_url: SafeUrl,
503 },
504 Esplora {
505 server_url: SafeUrl,
506 },
507}
508
509impl fmt::Display for ChainSource {
510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511 match self {
512 ChainSource::Bitcoind {
513 username: _,
514 password: _,
515 server_url,
516 } => {
517 write!(f, "Bitcoind source with URL: {server_url}")
518 }
519 ChainSource::Esplora { server_url } => {
520 write!(f, "Esplora source with URL: {server_url}")
521 }
522 }
523 }
524}
525
526#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
527#[serde(rename_all = "snake_case")]
528pub enum LightningInfo {
529 Connected {
530 public_key: PublicKey,
531 alias: String,
532 network: Network,
533 block_height: u64,
534 synced_to_chain: bool,
535 },
536 NotConnected,
537}
538
539#[derive(
540 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Encodable, Decodable, Serialize, Deserialize,
541)]
542#[serde(rename_all = "snake_case")]
543pub enum RegisteredProtocol {
544 Http,
545 Iroh,
546}
547
548#[derive(Debug, Serialize, Deserialize, Clone)]
549pub struct SetMnemonicPayload {
550 pub words: Option<String>,
551}