1use anchor_client::{solana_client::pubsub_client::PubsubClientError, solana_sdk};
2use gmsol_utils::token_config::TokenConfigError;
3use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
4
5pub use gmsol_store::CoreError;
6
7#[derive(Debug, thiserror::Error)]
9pub enum Error {
10 #[error("empty deposit")]
12 EmptyDeposit,
13 #[error("anchor: {0:#?}")]
15 Anchor(AnchorError),
16 #[error("{0:#?}")]
18 Client(Box<anchor_client::ClientError>),
19 #[error("model: {0}")]
21 Model(#[from] gmsol_model::Error),
22 #[error("numer out of range")]
24 NumberOutOfRange,
25 #[error("unknown: {0}")]
27 Unknown(String),
28 #[error("eyre: {0}")]
30 Eyre(#[from] eyre::Error),
31 #[error("missing return data")]
33 MissingReturnData,
34 #[error("base64: {0}")]
36 Base64(#[from] base64::DecodeError),
37 #[error("io: {0}")]
39 Io(#[from] std::io::Error),
40 #[error("not found")]
42 NotFound,
43 #[error("bytemuck: {0}")]
45 Bytemuck(bytemuck::PodCastError),
46 #[error("invalid argument: {0}")]
48 InvalidArgument(String),
49 #[error("fmt: {0}")]
51 Fmt(#[from] std::fmt::Error),
52 #[cfg(feature = "reqwest")]
54 #[error(transparent)]
55 Reqwest(#[from] reqwest::Error),
56 #[error("parse url: {0}")]
58 ParseUrl(#[from] url::ParseError),
59 #[cfg(all(feature = "eventsource-stream", feature = "reqwest"))]
61 #[error("sse: {0}")]
62 Sse(#[from] eventsource_stream::EventStreamError<reqwest::Error>),
63 #[error("json: {0}")]
65 Json(#[from] serde_json::Error),
66 #[cfg(feature = "decode")]
68 #[error("decode: {0}")]
69 Decode(#[from] gmsol_decode::DecodeError),
70 #[error("lagged: {0}")]
72 Lagged(#[from] BroadcastStreamRecvError),
73 #[error("pubsub: closed")]
75 PubsubClosed,
76 #[error("compile message: {0}")]
78 CompileMessage(#[from] solana_sdk::message::CompileError),
79 #[error("signer: {0}")]
81 SignerError(#[from] solana_sdk::signer::SignerError),
82 #[error("transport: {0}")]
84 Transport(String),
85 #[error("switchboard: {0}")]
87 Switchboard(String),
88 #[error(transparent)]
90 SolanaUtils(gmsol_solana_utils::Error),
91}
92
93impl Error {
94 pub fn unknown(msg: impl ToString) -> Self {
96 Self::Unknown(msg.to_string())
97 }
98
99 pub fn invalid_argument(msg: impl ToString) -> Self {
101 Self::InvalidArgument(msg.to_string())
102 }
103
104 pub fn transport(msg: impl ToString) -> Self {
106 Self::Transport(msg.to_string())
107 }
108
109 pub fn switchboard_error(msg: impl ToString) -> Self {
111 Self::Switchboard(msg.to_string())
112 }
113
114 pub fn anchor_error_code(&self) -> Option<u32> {
116 let Self::Anchor(error) = self else {
117 return None;
118 };
119 Some(error.error_code_number)
120 }
121}
122
123impl From<anchor_client::ClientError> for Error {
124 fn from(error: anchor_client::ClientError) -> Self {
125 use anchor_client::ClientError;
126
127 match error {
128 ClientError::AccountNotFound => Self::NotFound,
129 ClientError::SolanaClientError(error) => match handle_solana_client_error(&error) {
130 Some(err) => err,
131 None => Self::Client(Box::new(ClientError::SolanaClientError(error))),
132 },
133 ClientError::SolanaClientPubsubError(err) => Self::from(err),
134 err => Self::Client(Box::new(err)),
135 }
136 }
137}
138
139impl From<gmsol_solana_utils::Error> for Error {
140 fn from(value: gmsol_solana_utils::Error) -> Self {
141 match value {
142 gmsol_solana_utils::Error::Client(err) => match handle_solana_client_error(&err) {
143 Some(err) => err,
144 None => Self::SolanaUtils(err.into()),
145 },
146 err => Self::SolanaUtils(err),
147 }
148 }
149}
150
151impl From<anchor_client::anchor_lang::error::Error> for Error {
152 fn from(value: anchor_client::anchor_lang::error::Error) -> Self {
153 Self::Client(Box::new(value.into()))
154 }
155}
156
157impl From<PubsubClientError> for Error {
158 fn from(err: PubsubClientError) -> Self {
159 match err {
160 PubsubClientError::ConnectionClosed(_) => Self::PubsubClosed,
161 err => anchor_client::ClientError::from(err).into(),
162 }
163 }
164}
165
166impl From<TokenConfigError> for Error {
167 fn from(value: TokenConfigError) -> Self {
168 Self::from(anchor_client::anchor_lang::error::Error::from(
169 CoreError::from(value),
170 ))
171 }
172}
173
174#[derive(Debug)]
176pub struct AnchorError {
177 pub error_name: String,
179 pub error_code_number: u32,
181 pub error_msg: String,
183 pub error_origin: Option<ErrorOrigin>,
185 pub logs: Vec<String>,
187}
188
189#[derive(Debug)]
191pub enum ErrorOrigin {
192 Source(String, u32),
194 AccountName(String),
196}
197
198fn handle_solana_client_error(
199 error: &anchor_client::solana_client::client_error::ClientError,
200) -> Option<Error> {
201 use anchor_client::solana_client::{
202 client_error::ClientErrorKind,
203 rpc_request::{RpcError, RpcResponseErrorData},
204 };
205
206 let ClientErrorKind::RpcError(rpc_error) = error.kind() else {
207 return None;
208 };
209
210 let RpcError::RpcResponseError { data, .. } = rpc_error else {
211 return None;
212 };
213
214 let RpcResponseErrorData::SendTransactionPreflightFailure(simulation) = data else {
215 return None;
216 };
217
218 let Some(logs) = &simulation.logs else {
219 return None;
220 };
221
222 for log in logs {
223 if log.starts_with("Program log: AnchorError") {
224 let log = log.trim_start_matches("Program log: AnchorError ");
225 let Some((origin, rest)) = log.split_once("Error Code:") else {
226 break;
227 };
228 let Some((name, rest)) = rest.split_once("Error Number:") else {
229 break;
230 };
231 let Some((number, message)) = rest.split_once("Error Message:") else {
232 break;
233 };
234 let number = number.trim().trim_end_matches('.');
235 let Ok(number) = number.parse() else {
236 break;
237 };
238
239 let origin = origin.trim().trim_end_matches('.');
240
241 let origin = if origin.starts_with("thrown in") {
242 let source = origin.trim_start_matches("thrown in ");
243 if let Some((filename, line)) = source.split_once(':') {
244 Some(ErrorOrigin::Source(
245 filename.to_string(),
246 line.parse().ok().unwrap_or(0),
247 ))
248 } else {
249 None
250 }
251 } else if origin.starts_with("caused by account:") {
252 let account = origin.trim_start_matches("caused by account: ");
253 Some(ErrorOrigin::AccountName(account.to_string()))
254 } else {
255 None
256 };
257
258 let error = AnchorError {
259 error_name: name.trim().trim_end_matches('.').to_string(),
260 error_code_number: number,
261 error_msg: message.trim().to_string(),
262 error_origin: origin,
263 logs: logs.clone(),
264 };
265
266 return Some(Error::Anchor(error));
267 }
268 }
269
270 None
271}
272
273impl<T> From<(T, gmsol_solana_utils::Error)> for Error {
274 fn from((_, err): (T, gmsol_solana_utils::Error)) -> Self {
275 Self::SolanaUtils(err)
276 }
277}