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