1pub(crate) use gmsol_programs::anchor_lang::prelude::Error as AnchorLangError;
2
3#[derive(Debug, thiserror::Error)]
5pub enum Error {
6 #[error("solana-utils: {0}")]
8 SolanaUtils(gmsol_solana_utils::Error),
9 #[error("anchor-lang: {0}")]
11 AnchorLang(Box<AnchorLangError>),
12 #[error("anchor: {0:#?}")]
14 Anchor(AnchorError),
15 #[error("model: {0}")]
17 Model(#[from] gmsol_model::Error),
18 #[error("base64-decode: {0}")]
20 Base64Decode(#[from] base64::DecodeError),
21 #[cfg(feature = "bincode")]
23 #[error("bincode: {0}")]
24 Bincode(#[from] bincode::Error),
25 #[error("custom: {0}")]
27 Custom(String),
28 #[error("transport: {0}")]
30 Transport(String),
31 #[cfg(feature = "market-graph")]
33 #[error("market-graph: {0}")]
34 MarketGraph(#[from] crate::market_graph::error::MarketGraphError),
35 #[error("parse pubkey error: {0}")]
37 ParsePubkey(#[from] solana_sdk::pubkey::ParsePubkeyError),
38 #[cfg(feature = "client")]
40 #[error("pubsub: closed")]
41 PubsubClosed,
42 #[error("not found")]
44 NotFound,
45 #[cfg(feature = "decode")]
47 #[error("decode: {0}")]
48 Decode(#[from] gmsol_decode::DecodeError),
49 #[error("programs: {0}")]
51 Programs(#[from] gmsol_programs::Error),
52 #[cfg(feature = "reqwest")]
54 #[error("reqwest: {0}")]
55 Reqwest(#[from] reqwest::Error),
56 #[cfg(feature = "serde_json")]
58 #[error("json: {0}")]
59 Json(#[from] serde_json::Error),
60 #[error("market closed: {0}")]
62 MarketClosed(String),
63 #[error(transparent)]
65 ConvertInterger(#[from] std::num::TryFromIntError),
66}
67
68impl Error {
69 pub fn custom(msg: impl ToString) -> Self {
71 Self::Custom(msg.to_string())
72 }
73
74 pub fn transport(msg: impl ToString) -> Self {
76 Self::Transport(msg.to_string())
77 }
78
79 pub fn anchor_error_code(&self) -> Option<u32> {
81 let Self::Anchor(error) = self else {
82 return None;
83 };
84 Some(error.error_code_number)
85 }
86
87 pub fn market_closed(msg: impl ToString) -> Self {
89 Self::MarketClosed(msg.to_string())
90 }
91}
92
93impl From<AnchorLangError> for Error {
94 fn from(value: AnchorLangError) -> Self {
95 Self::AnchorLang(Box::new(value))
96 }
97}
98
99#[cfg(feature = "wasm-bindgen")]
100impl From<Error> for wasm_bindgen::JsValue {
101 fn from(value: Error) -> Self {
102 Self::from_str(&value.to_string())
103 }
104}
105
106#[derive(Debug, Clone, Default)]
108pub struct AnchorError {
109 pub error_name: String,
111 pub error_code_number: u32,
113 pub error_msg: String,
115 pub error_origin: Option<ErrorOrigin>,
117 pub logs: Vec<String>,
119}
120
121#[derive(Debug, Clone)]
123pub enum ErrorOrigin {
124 Source(String, u32),
126 AccountName(String),
128}
129
130#[cfg(feature = "solana-client")]
131fn handle_solana_client_error(error: &solana_client::client_error::ClientError) -> Option<Error> {
132 use solana_client::{
133 client_error::ClientErrorKind,
134 rpc_request::{RpcError, RpcResponseErrorData},
135 };
136
137 let ClientErrorKind::RpcError(rpc_error) = error.kind() else {
138 return None;
139 };
140
141 let RpcError::RpcResponseError { data, .. } = rpc_error else {
142 return None;
143 };
144
145 let RpcResponseErrorData::SendTransactionPreflightFailure(simulation) = data else {
146 return None;
147 };
148
149 let Some(logs) = &simulation.logs else {
150 return None;
151 };
152
153 for log in logs {
154 if log.starts_with("Program log: AnchorError") {
155 let log = log.trim_start_matches("Program log: AnchorError ");
156 let Some((origin, rest)) = log.split_once("Error Code:") else {
157 break;
158 };
159 let Some((name, rest)) = rest.split_once("Error Number:") else {
160 break;
161 };
162 let Some((number, message)) = rest.split_once("Error Message:") else {
163 break;
164 };
165 let number = number.trim().trim_end_matches('.');
166 let Ok(number) = number.parse() else {
167 break;
168 };
169
170 let origin = origin.trim().trim_end_matches('.');
171
172 let origin = if origin.starts_with("thrown in") {
173 let source = origin.trim_start_matches("thrown in ");
174 if let Some((filename, line)) = source.split_once(':') {
175 Some(ErrorOrigin::Source(
176 filename.to_string(),
177 line.parse().ok().unwrap_or(0),
178 ))
179 } else {
180 None
181 }
182 } else if origin.starts_with("caused by account:") {
183 let account = origin.trim_start_matches("caused by account: ");
184 Some(ErrorOrigin::AccountName(account.to_string()))
185 } else {
186 None
187 };
188
189 let error = AnchorError {
190 error_name: name.trim().trim_end_matches('.').to_string(),
191 error_code_number: number,
192 error_msg: message.trim().to_string(),
193 error_origin: origin,
194 logs: logs.clone(),
195 };
196
197 return Some(Error::Anchor(error));
198 }
199 }
200
201 None
202}
203
204impl From<gmsol_solana_utils::Error> for Error {
205 fn from(value: gmsol_solana_utils::Error) -> Self {
206 match value {
207 #[cfg(feature = "solana-client")]
208 gmsol_solana_utils::Error::Client(err) => match handle_solana_client_error(&err) {
209 Some(err) => err,
210 None => Self::SolanaUtils(err.into()),
211 },
212 err => Self::SolanaUtils(err),
213 }
214 }
215}
216
217impl<T> From<(T, gmsol_solana_utils::Error)> for Error {
218 fn from((_, err): (T, gmsol_solana_utils::Error)) -> Self {
219 Self::from(err)
220 }
221}