1use std::str::FromStr;
2
3use bitcoin::secp256k1::PublicKey;
4use bitcoin::secp256k1::schnorr::Signature;
5use fedimint_connectors::error::ServerError;
6use fedimint_core::config::FederationId;
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::util::SafeUrl;
9use fedimint_core::{Amount, OutPoint, apply, async_trait_maybe_send};
10use fedimint_ln_common::client::GatewayApi;
11use lightning_invoice::{Bolt11Invoice, RoutingFees};
12use reqwest::Method;
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16use crate::contracts::{IncomingContract, OutgoingContract};
17use crate::endpoint_constants::{
18 CREATE_BOLT11_INVOICE_ENDPOINT, ROUTING_INFO_ENDPOINT, SEND_PAYMENT_ENDPOINT,
19};
20use crate::{Bolt11InvoiceDescription, LightningInvoice};
21
22#[apply(async_trait_maybe_send!)]
23pub trait GatewayConnection: std::fmt::Debug {
24 async fn routing_info(
25 &self,
26 gateway_api: SafeUrl,
27 federation_id: &FederationId,
28 ) -> Result<Option<RoutingInfo>, ServerError>;
29
30 async fn bolt11_invoice(
31 &self,
32 gateway_api: SafeUrl,
33 federation_id: FederationId,
34 contract: IncomingContract,
35 amount: Amount,
36 description: Bolt11InvoiceDescription,
37 expiry_secs: u32,
38 ) -> Result<Bolt11Invoice, ServerError>;
39
40 async fn send_payment(
41 &self,
42 gateway_api: SafeUrl,
43 federation_id: FederationId,
44 outpoint: OutPoint,
45 contract: OutgoingContract,
46 invoice: LightningInvoice,
47 auth: Signature,
48 ) -> Result<Result<[u8; 32], Signature>, ServerError>;
49}
50
51#[derive(Debug, Clone)]
52pub struct RealGatewayConnection {
53 pub api: GatewayApi,
54}
55
56#[apply(async_trait_maybe_send!)]
57impl GatewayConnection for RealGatewayConnection {
58 async fn routing_info(
59 &self,
60 gateway_api: SafeUrl,
61 federation_id: &FederationId,
62 ) -> Result<Option<RoutingInfo>, ServerError> {
63 self.api
64 .request(
65 &gateway_api,
66 Method::POST,
67 ROUTING_INFO_ENDPOINT,
68 Some(federation_id),
69 )
70 .await
71 }
72
73 async fn bolt11_invoice(
74 &self,
75 gateway_api: SafeUrl,
76 federation_id: FederationId,
77 contract: IncomingContract,
78 amount: Amount,
79 description: Bolt11InvoiceDescription,
80 expiry_secs: u32,
81 ) -> Result<Bolt11Invoice, ServerError> {
82 self.api
83 .request(
84 &gateway_api,
85 Method::POST,
86 CREATE_BOLT11_INVOICE_ENDPOINT,
87 Some(CreateBolt11InvoicePayload {
88 federation_id,
89 contract,
90 amount,
91 description,
92 expiry_secs,
93 }),
94 )
95 .await
96 }
97
98 async fn send_payment(
99 &self,
100 gateway_api: SafeUrl,
101 federation_id: FederationId,
102 outpoint: OutPoint,
103 contract: OutgoingContract,
104 invoice: LightningInvoice,
105 auth: Signature,
106 ) -> Result<Result<[u8; 32], Signature>, ServerError> {
107 self.api
108 .request(
109 &gateway_api,
110 Method::POST,
111 SEND_PAYMENT_ENDPOINT,
112 Some(SendPaymentPayload {
113 federation_id,
114 outpoint,
115 contract,
116 invoice,
117 auth,
118 }),
119 )
120 .await
121 }
122}
123
124pub const MAX_INVOICE_EXPIRY_SECS: u32 = 60 * 60 * 24;
129
130#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
131pub struct CreateBolt11InvoicePayload {
132 pub federation_id: FederationId,
133 pub contract: IncomingContract,
134 pub amount: Amount,
135 pub description: Bolt11InvoiceDescription,
136 pub expiry_secs: u32,
137}
138
139#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
140pub struct SendPaymentPayload {
141 pub federation_id: FederationId,
142 pub outpoint: OutPoint,
143 pub contract: OutgoingContract,
144 pub invoice: LightningInvoice,
145 pub auth: Signature,
146}
147
148#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
149pub struct RoutingInfo {
150 pub lightning_public_key: PublicKey,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub lightning_alias: Option<String>,
160 pub module_public_key: PublicKey,
163 pub send_fee_minimum: PaymentFee,
166 pub send_fee_default: PaymentFee,
170 pub expiration_delta_minimum: u64,
174 pub expiration_delta_default: u64,
179 pub receive_fee: PaymentFee,
181}
182
183impl RoutingInfo {
184 pub fn send_parameters(&self, invoice: &Bolt11Invoice) -> (PaymentFee, u64) {
185 if invoice.recover_payee_pub_key() == self.lightning_public_key {
186 (self.send_fee_minimum, self.expiration_delta_minimum)
187 } else {
188 (self.send_fee_default, self.expiration_delta_default)
189 }
190 }
191}
192
193#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable, Copy)]
194pub struct PaymentFee {
195 pub base: Amount,
196 pub parts_per_million: u64,
197}
198
199impl PaymentFee {
200 pub const SEND_FEE_LIMIT: PaymentFee = PaymentFee {
204 base: Amount::from_sats(100),
205 parts_per_million: 15_000,
206 };
207
208 pub const TRANSACTION_FEE_DEFAULT: PaymentFee = PaymentFee {
211 base: Amount::from_sats(2),
212 parts_per_million: 3000,
213 };
214
215 pub const RECEIVE_FEE_LIMIT: PaymentFee = PaymentFee {
218 base: Amount::from_sats(50),
219 parts_per_million: 5_000,
220 };
221
222 pub fn is_within(&self, limit: &PaymentFee) -> bool {
230 self.base <= limit.base && self.parts_per_million <= limit.parts_per_million
231 }
232
233 pub fn checked_add(self, rhs: Self) -> Option<PaymentFee> {
239 Some(PaymentFee {
240 base: self.base.checked_add(rhs.base)?,
241 parts_per_million: self.parts_per_million.checked_add(rhs.parts_per_million)?,
242 })
243 }
244
245 pub fn add_to(&self, msats: u64) -> Amount {
246 Amount::from_msats(msats.saturating_add(self.absolute_fee(msats)))
247 }
248
249 pub fn subtract_from(&self, msats: u64) -> Amount {
250 Amount::from_msats(msats.saturating_sub(self.absolute_fee(msats)))
251 }
252
253 pub fn fee(&self, msats: u64) -> Amount {
254 Amount::from_msats(self.absolute_fee(msats))
255 }
256
257 fn absolute_fee(&self, msats: u64) -> u64 {
258 msats
262 .saturating_mul(self.parts_per_million)
263 .saturating_div(1_000_000)
264 .saturating_add(self.base.msats)
265 }
266}
267
268#[derive(Debug, Error)]
271#[error("Payment fee {0} exceeds the range of RoutingFees")]
272pub struct FeeOutOfRangeError(PaymentFee);
273
274impl From<RoutingFees> for PaymentFee {
275 fn from(value: RoutingFees) -> Self {
276 PaymentFee {
277 base: Amount::from_msats(u64::from(value.base_msat)),
278 parts_per_million: u64::from(value.proportional_millionths),
279 }
280 }
281}
282
283impl TryFrom<PaymentFee> for RoutingFees {
284 type Error = FeeOutOfRangeError;
285
286 fn try_from(value: PaymentFee) -> Result<Self, Self::Error> {
287 Ok(RoutingFees {
288 base_msat: u32::try_from(value.base.msats).map_err(|_| FeeOutOfRangeError(value))?,
289 proportional_millionths: u32::try_from(value.parts_per_million)
290 .map_err(|_| FeeOutOfRangeError(value))?,
291 })
292 }
293}
294
295impl std::fmt::Display for PaymentFee {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 write!(f, "{},{}", self.base, self.parts_per_million)
298 }
299}
300
301impl FromStr for PaymentFee {
302 type Err = anyhow::Error;
303
304 fn from_str(s: &str) -> Result<Self, Self::Err> {
305 let mut parts = s.split(',');
306 let base_str = parts
307 .next()
308 .ok_or(anyhow::anyhow!("Failed to parse base fee"))?;
309 let ppm_str = parts.next().ok_or(anyhow::anyhow!("Failed to parse ppm"))?;
310
311 if parts.next().is_some() {
313 return Err(anyhow::anyhow!(
314 "Failed to parse fees. Expected format <base>,<ppm>"
315 ));
316 }
317
318 let base = Amount::from_str(base_str)?;
319 let parts_per_million = ppm_str.parse::<u64>()?;
320
321 Ok(PaymentFee {
322 base,
323 parts_per_million,
324 })
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use fedimint_core::Amount;
331 use lightning_invoice::RoutingFees;
332
333 use super::PaymentFee;
334
335 #[test]
338 fn is_within_enforces_both_components() {
339 let over_ppm = PaymentFee {
341 base: Amount::from_sats(0),
342 parts_per_million: 1_000_000,
343 };
344 assert!(!over_ppm.is_within(&PaymentFee::SEND_FEE_LIMIT));
345
346 let over_ppm = PaymentFee {
347 base: Amount::from_sats(0),
348 parts_per_million: 5_000_000,
349 };
350 assert!(!over_ppm.is_within(&PaymentFee::SEND_FEE_LIMIT));
351
352 let over_ppm = PaymentFee {
354 base: Amount::from_sats(0),
355 parts_per_million: 500_000,
356 };
357 assert!(!over_ppm.is_within(&PaymentFee::RECEIVE_FEE_LIMIT));
358
359 let over_base = PaymentFee {
361 base: Amount::from_sats(101),
362 parts_per_million: 0,
363 };
364 assert!(!over_base.is_within(&PaymentFee::SEND_FEE_LIMIT));
365
366 assert!(PaymentFee::SEND_FEE_LIMIT.is_within(&PaymentFee::SEND_FEE_LIMIT));
368
369 let ok = PaymentFee {
371 base: Amount::from_sats(50),
372 parts_per_million: 10_000,
373 };
374 assert!(ok.is_within(&PaymentFee::SEND_FEE_LIMIT));
375 }
376
377 #[test]
380 fn checked_add_reports_overflow_instead_of_panicking() {
381 let max_base = PaymentFee {
382 base: Amount::from_msats(u64::MAX),
383 parts_per_million: 0,
384 };
385 let one_msat = PaymentFee {
386 base: Amount::from_msats(1),
387 parts_per_million: 0,
388 };
389 assert!(max_base.checked_add(one_msat).is_none());
390
391 let max_ppm = PaymentFee {
392 base: Amount::ZERO,
393 parts_per_million: u64::MAX,
394 };
395 let one_ppm = PaymentFee {
396 base: Amount::ZERO,
397 parts_per_million: 1,
398 };
399 assert!(max_ppm.checked_add(one_ppm).is_none());
400
401 assert_eq!(
402 PaymentFee::TRANSACTION_FEE_DEFAULT
403 .checked_add(PaymentFee::TRANSACTION_FEE_DEFAULT)
404 .expect("Two default fees fit"),
405 PaymentFee {
406 base: Amount::from_sats(4),
407 parts_per_million: 6_000,
408 }
409 );
410 }
411
412 #[test]
417 fn routing_fees_conversion_rejects_out_of_range_fees() {
418 let over_base = PaymentFee {
419 base: Amount::from_msats(u64::from(u32::MAX) + 1),
420 parts_per_million: 0,
421 };
422 assert!(RoutingFees::try_from(over_base).is_err());
423
424 let over_ppm = PaymentFee {
425 base: Amount::ZERO,
426 parts_per_million: u64::from(u32::MAX) + 1,
427 };
428 assert!(RoutingFees::try_from(over_ppm).is_err());
429
430 let fees = RoutingFees::try_from(PaymentFee::SEND_FEE_LIMIT)
431 .expect("The send fee limit is within range");
432 assert_eq!(fees.base_msat, 100_000);
433 assert_eq!(fees.proportional_millionths, 15_000);
434 }
435
436 #[test]
440 fn absolute_fee_saturates_instead_of_panicking() {
441 let huge = PaymentFee {
442 base: Amount::from_msats(u64::MAX),
443 parts_per_million: u64::MAX,
444 };
445 assert_eq!(huge.fee(u64::MAX), Amount::from_msats(u64::MAX));
446
447 assert_eq!(
448 PaymentFee::TRANSACTION_FEE_DEFAULT.fee(1_000_000),
449 Amount::from_msats(2_000 + 3_000)
450 );
451 }
452}