1use std::fmt::Display;
2
3use axum::Json;
4use axum::body::Body;
5use axum::response::{IntoResponse, Response};
6use fedimint_core::config::{FederationId, FederationIdPrefix};
7use fedimint_core::crit;
8use fedimint_core::envs::is_env_var_set;
9use fedimint_core::fmt_utils::OptStacktrace;
10use fedimint_core::util::FmtCompactAnyhow;
11use fedimint_gw_client::pay::OutgoingPaymentError;
12use fedimint_lightning::LightningRpcError;
13use fedimint_logging::LOG_GATEWAY;
14use reqwest::StatusCode;
15use thiserror::Error;
16
17use crate::envs::FM_DEBUG_GATEWAY_ENV;
18
19#[derive(Debug, thiserror::Error)]
21pub enum GatewayError {
22 #[error("Admin error: {0}")]
23 Admin(#[from] AdminGatewayError),
24 #[error("Public error: {0}")]
25 Public(#[from] PublicGatewayError),
26 #[error("{0}")]
27 Lnurl(#[from] LnurlError),
28}
29
30impl IntoResponse for GatewayError {
31 fn into_response(self) -> Response {
32 match self {
33 GatewayError::Admin(admin) => admin.into_response(),
34 GatewayError::Public(public) => public.into_response(),
35 GatewayError::Lnurl(lnurl) => lnurl.into_response(),
36 }
37 }
38}
39
40#[derive(Debug, Error)]
44pub enum PublicGatewayError {
45 #[error("Lightning rpc error: {}", .0)]
46 Lightning(#[from] LightningRpcError),
47 #[error("LNv1 error: {:?}", .0)]
48 LNv1(#[from] LNv1Error),
49 #[error("LNv2 error: {:?}", .0)]
50 LNv2(#[from] LNv2Error),
51 #[error("{}", .0)]
52 FederationNotConnected(#[from] FederationNotConnected),
53 #[error("Failed to receive ecash: {failure_reason}")]
54 ReceiveEcashError { failure_reason: String },
55 #[error("Unexpected Error: {}", OptStacktrace(.0))]
56 Unexpected(#[from] anyhow::Error),
57}
58
59impl IntoResponse for PublicGatewayError {
60 fn into_response(self) -> Response {
61 crit!(target: LOG_GATEWAY, "{self}");
65 let (error_message, status_code) = match &self {
66 PublicGatewayError::FederationNotConnected(e) => {
67 (e.to_string(), StatusCode::BAD_REQUEST)
68 }
69 PublicGatewayError::ReceiveEcashError { .. } => (
70 "Failed to receive ecash".to_string(),
71 StatusCode::INTERNAL_SERVER_ERROR,
72 ),
73 PublicGatewayError::Lightning(_) => (
74 "Lightning Network operation failed".to_string(),
75 StatusCode::INTERNAL_SERVER_ERROR,
76 ),
77 PublicGatewayError::LNv1(_) => (
78 "LNv1 operation failed, please contact gateway operator".to_string(),
79 StatusCode::INTERNAL_SERVER_ERROR,
80 ),
81 PublicGatewayError::LNv2(_) => (
82 "LNv2 operation failed, please contact gateway operator".to_string(),
83 StatusCode::INTERNAL_SERVER_ERROR,
84 ),
85 PublicGatewayError::Unexpected(e) => (e.to_string(), StatusCode::BAD_REQUEST),
86 };
87
88 let error_message = if is_env_var_set(FM_DEBUG_GATEWAY_ENV) {
89 self.to_string()
90 } else {
91 error_message
92 };
93
94 Response::builder()
95 .status(status_code)
96 .body(error_message.into())
97 .expect("Failed to create Response")
98 }
99}
100
101#[derive(Debug, Error)]
104pub enum AdminGatewayError {
105 #[error("Failed to create a federation client")]
106 ClientCreationError(anyhow::Error),
107 #[error("Failed to remove a federation client: {0}")]
108 ClientRemovalError(String),
109 #[error("There was an error with the Gateway's mnemonic: {}", .0.fmt_compact_anyhow())]
110 MnemonicError(anyhow::Error),
111 #[error("Unexpected Error: {}", .0.fmt_compact_anyhow())]
112 Unexpected(#[from] anyhow::Error),
113 #[error("{}", .0)]
114 FederationNotConnected(#[from] FederationNotConnected),
115 #[error("Error configuring the gateway: {0}")]
116 GatewayConfigurationError(String),
117 #[error("Lightning error: {}", .0)]
118 Lightning(#[from] LightningRpcError),
119 #[error("Error registering federation {federation_id}")]
120 RegistrationError { federation_id: FederationId },
121 #[error("Error withdrawing funds onchain: {failure_reason}")]
122 WithdrawError { failure_reason: String },
123}
124
125impl IntoResponse for AdminGatewayError {
126 fn into_response(self) -> Response {
129 crit!(target: LOG_GATEWAY, "{self}");
130 Response::builder()
131 .status(StatusCode::INTERNAL_SERVER_ERROR)
132 .body(self.to_string().into())
133 .expect("Failed to create Response")
134 }
135}
136
137#[derive(Debug, Error)]
140pub enum LNv1Error {
141 #[error("Incoming payment error: {}", OptStacktrace(.0))]
142 IncomingPayment(String),
143 #[error(
144 "Outgoing Contract Error Reason: {message} Stack: {}",
145 OptStacktrace(error)
146 )]
147 OutgoingContract {
148 error: Box<OutgoingPaymentError>,
149 message: String,
150 },
151 #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
152 OutgoingPayment(#[from] anyhow::Error),
153}
154
155#[derive(Debug, Error)]
158pub enum LNv2Error {
159 #[error("Incoming Payment Error: {}", .0)]
160 IncomingPayment(String),
161 #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
162 OutgoingPayment(#[from] anyhow::Error),
163}
164
165#[derive(Debug, Error)]
168pub struct FederationNotConnected {
169 pub federation_id_prefix: FederationIdPrefix,
170}
171
172impl Display for FederationNotConnected {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 write!(
175 f,
176 "No federation available for prefix {}",
177 self.federation_id_prefix
178 )
179 }
180}
181
182#[derive(Debug, Error)]
184pub(crate) struct LnurlError {
185 code: StatusCode,
186 reason: anyhow::Error,
187}
188
189impl Display for LnurlError {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "LNURL Error: {}", self.reason,)
192 }
193}
194
195impl LnurlError {
196 pub(crate) fn internal(reason: anyhow::Error) -> Self {
197 Self {
198 code: StatusCode::INTERNAL_SERVER_ERROR,
199 reason,
200 }
201 }
202}
203
204impl IntoResponse for LnurlError {
205 fn into_response(self) -> Response<Body> {
206 let json = Json(serde_json::json!({
207 "status": "ERROR",
208 "reason": self.reason.to_string(),
209 }));
210
211 (self.code, json).into_response()
212 }
213}