diff --git a/crates/solvers/src/infra/dex/bitget/dto.rs b/crates/solvers/src/infra/dex/bitget/dto.rs index 93122a3393..fba311354a 100644 --- a/crates/solvers/src/infra/dex/bitget/dto.rs +++ b/crates/solvers/src/infra/dex/bitget/dto.rs @@ -146,12 +146,24 @@ impl SwapTransaction { /// A Bitget API response wrapper. /// /// On success `status` is 0 and `data` contains the result. -/// On error `status` is non-zero and `data` is null. +/// On error `status` is 1, `error_code` identifies the failure, and `data` is +/// null. +/// +/// See #[derive(Deserialize, Clone, Debug)] pub struct Response { - /// Response status code (0 = success). + /// Response status code (0 = success, 1 = failure). pub status: i64, + /// Bitget error code (e.g. 80005 for insufficient liquidity). + /// Only present when `status` is non-zero. + #[serde(default)] + pub error_code: Option, + + /// Human-readable error description. + #[serde(default)] + pub message: Option, + /// Response data — `None` when the API returns an error. pub data: Option, } diff --git a/crates/solvers/src/infra/dex/bitget/mod.rs b/crates/solvers/src/infra/dex/bitget/mod.rs index 0967053575..ed05a3dea4 100644 --- a/crates/solvers/src/infra/dex/bitget/mod.rs +++ b/crates/solvers/src/infra/dex/bitget/mod.rs @@ -214,13 +214,31 @@ impl Bitget { Ok(BASE64_STANDARD.encode(signature)) } - /// Bitget error handling based on status codes. - fn handle_api_error(status: i64, body: String) -> Result<(), Error> { - Err(match status { - 0 => return Ok(()), - 429 => Error::RateLimited, - 404 => Error::NotFound, - _ => Error::Api { status, body }, + /// Bitget error handling based on `error_code`. + /// + /// See + fn handle_api_error( + status: i64, + error_code: Option, + message: String, + ) -> Result<(), Error> { + if status == 0 { + return Ok(()); + } + + Err(match error_code.unwrap_or(80000) { + 80001 // Insufficient token balance + | 80004 // Order expired + | 80005 // Insufficient liquidity + | 80008 // Reverse quote did not converge + | 80009 // Token info not found + | 80010 // Price/gas price not found + => Error::NotFound, + 80002 // Amount below minimum + | 80003 // Amount above maximum + | 80006 // Illegal request + => Error::BadRequest, + code => Error::Api { code, message }, }) } @@ -256,6 +274,9 @@ impl Bitget { let status = response.status(); let body = response.text().await.map_err(util::http::Error::from)?; + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(Error::RateLimited); + } if !status.is_success() { return Err(util::http::Error::Status(status, body).into()); } @@ -263,7 +284,11 @@ impl Bitget { let response: dto::Response = serde_json::from_str(&body).map_err(util::http::Error::from)?; - Self::handle_api_error(response.status, body)?; + Self::handle_api_error( + response.status, + response.error_code, + response.message.unwrap_or_default(), + )?; response.data.ok_or(Error::NotFound) } } @@ -294,8 +319,10 @@ pub enum Error { AmountConversionFailed, #[error("decimals are missing for the swapped tokens")] MissingDecimals, - #[error("api error status {status}: {body}")] - Api { status: i64, body: String }, + #[error("bad request")] + BadRequest, + #[error("api error code {code}: {message}")] + Api { code: i64, message: String }, #[error(transparent)] Http(#[from] util::http::Error), } diff --git a/crates/solvers/src/infra/dex/mod.rs b/crates/solvers/src/infra/dex/mod.rs index 7050be1436..22c11a6973 100644 --- a/crates/solvers/src/infra/dex/mod.rs +++ b/crates/solvers/src/infra/dex/mod.rs @@ -113,7 +113,7 @@ impl From for Error { match err { bitget::Error::OrderNotSupported => Self::OrderNotSupported, bitget::Error::NotFound => Self::NotFound, - bitget::Error::MissingDecimals => Self::BadRequest, + bitget::Error::MissingDecimals | bitget::Error::BadRequest => Self::BadRequest, bitget::Error::RateLimited => Self::RateLimited, _ => Self::Other(Box::new(err)), } diff --git a/crates/solvers/src/tests/bitget/not_found.rs b/crates/solvers/src/tests/bitget/not_found.rs index 9c538e42ee..e99f5005b3 100644 --- a/crates/solvers/src/tests/bitget/not_found.rs +++ b/crates/solvers/src/tests/bitget/not_found.rs @@ -9,13 +9,15 @@ use { #[tokio::test] async fn sell_no_liquidity() { let api = mock::http::setup(vec![ - // Swap request returns an error status. + // Swap request returns an error status (insufficient liquidity). mock::http::Expectation::Post { path: mock::http::Path::exact("bgw-pro/swapx/pro/swap"), req: mock::http::RequestBody::Any, res: json!({ - "status": 404, - "data": {} + "status": 1, + "error_code": 80005, + "message": "Insufficient liquidity; transaction cannot be completed at this time.", + "data": null }), }, ])