Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,32 @@ impl BpxClient {
Ok(res)
}

/// Deserializes the response body as JSON, logging the body at error level on failure.
///
/// Unlike `Response::json()`, this method captures the response body and logs it
/// if deserialization fails, making it easier to debug API changes.
pub async fn json_with_context<T: serde::de::DeserializeOwned>(res: Response) -> Result<T> {
let body = res.text().await?;
serde_json::from_str(&body).map_err(|error| {
// Truncate very long responses for readability in logs
let body_preview = if body.len() > 2000 {
format!(
"{}...[truncated, {} bytes total]",
&body[..2000],
body.len()
)
} else {
body
};
tracing::error!(
%error,
body_preview,
"Failed to deserialize API response"
);
error.into()
})
}

/// Sends a GET request to the specified URL and signs it before execution.
pub async fn get<U: IntoUrl>(&self, url: U) -> Result<Response> {
let req = self.build_and_maybe_sign_request::<(), _>(url, Method::GET, None)?;
Expand Down
8 changes: 4 additions & 4 deletions client/src/routes/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ impl BpxClient {
pub async fn get_account(&self) -> Result<AccountSettings> {
let url = self.base_url.join(API_ACCOUNT)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the account's maximum borrow amount for a given symbol.
pub async fn get_account_max_borrow(&self, symbol: &str) -> Result<AccountMaxBorrow> {
let mut url = self.base_url.join(API_ACCOUNT_MAX_BORROW)?;
url.query_pairs_mut().append_pair("symbol", symbol);
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the account's maximum order amount for a given symbol.
Expand All @@ -39,7 +39,7 @@ impl BpxClient {
.map_err(|e| Error::UrlParseError(e.to_string().into_boxed_str()))?;
url.set_query(Some(&query_string));
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the account's maximum withdrawal amount for a given symbol.
Expand All @@ -61,7 +61,7 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Updates the account's settings.
Expand Down
2 changes: 1 addition & 1 deletion client/src/routes/borrow_lend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ impl BpxClient {
pub async fn get_borrow_lend_positions(&self) -> Result<Vec<BorrowLendPosition>> {
let url = self.base_url.join(API_BORROW_LEND_POSITIONS)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
12 changes: 6 additions & 6 deletions client/src/routes/capital.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl BpxClient {
pub async fn get_balances(&self) -> Result<HashMap<String, Balance>> {
let url = self.base_url.join(API_CAPITAL)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Retrieves a list of deposits with optional pagination.
Expand All @@ -44,7 +44,7 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the deposit address for a specified blockchain.
Expand All @@ -53,7 +53,7 @@ impl BpxClient {
url.query_pairs_mut()
.append_pair("blockchain", &blockchain.to_string());
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Retrieves a list of withdrawals with optional pagination.
Expand All @@ -73,7 +73,7 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Submits a withdrawal request for the specified payload.
Expand All @@ -83,13 +83,13 @@ impl BpxClient {
) -> Result<Withdrawal> {
let endpoint = self.base_url.join(API_WITHDRAWALS)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the subaccount's collateral information.
pub async fn get_collateral(&self) -> Result<Collateral> {
let url = self.base_url.join(API_COLLATERAL)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
2 changes: 1 addition & 1 deletion client/src/routes/futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ impl BpxClient {
pub async fn get_open_future_positions(&self) -> Result<Vec<FuturePosition>> {
let url = self.base_url.join(API_FUTURES_POSITION)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
2 changes: 1 addition & 1 deletion client/src/routes/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ impl BpxClient {
let mut url = self.base_url.join(API_FILLS_HISTORY)?;
url.set_query(Some(&query_string));
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
16 changes: 8 additions & 8 deletions client/src/routes/markets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,36 @@ impl BpxClient {
pub async fn get_assets(&self) -> Result<Vec<Asset>> {
let url = self.base_url.join(API_ASSETS)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Retrieves a list of available markets.
pub async fn get_markets(&self) -> Result<Vec<Market>> {
let url = self.base_url.join(API_MARKETS)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Retrieves mark price, index price and the funding rate for the current interval for all symbols, or the symbol specified.
pub async fn get_all_mark_prices(&self) -> Result<Vec<MarkPrice>> {
let url = self.base_url.join(API_MARK_PRICES)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the ticker information for a given symbol.
pub async fn get_ticker(&self, symbol: &str) -> Result<Ticker> {
let mut url = self.base_url.join(API_TICKER)?;
url.query_pairs_mut().append_pair("symbol", symbol);
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches the ticker information for all symbols.
pub async fn get_tickers(&self) -> Result<Vec<Ticker>> {
let url = self.base_url.join(API_TICKERS)?;
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Retrieves the order book depth for a given symbol.
Expand All @@ -63,15 +63,15 @@ impl BpxClient {
url.query_pairs_mut().append_pair("limit", limit.as_ref());
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Funding interval rate history for futures.
pub async fn get_funding_interval_rates(&self, symbol: &str) -> Result<Vec<FundingRate>> {
let mut url = self.base_url.join(API_FUNDING)?;
url.query_pairs_mut().append_pair("symbol", symbol);
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches historical K-line (candlestick) data for a given symbol and interval.
Expand All @@ -93,6 +93,6 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
10 changes: 5 additions & 5 deletions client/src/routes/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Executes a new order with the given payload.
pub async fn execute_order(&self, payload: ExecuteOrderPayload) -> Result<Order> {
let endpoint = self.base_url.join(API_ORDER)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Cancels a specific order by symbol and either order ID or client ID.
Expand All @@ -61,7 +61,7 @@ impl BpxClient {
};

let res = self.delete(url, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Retrieves all open orders, optionally filtered by symbol.
Expand All @@ -71,13 +71,13 @@ impl BpxClient {
url.query_pairs_mut().append_pair("symbol", s);
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Cancels all open orders matching the specified payload.
pub async fn cancel_open_orders(&self, payload: CancelOpenOrdersPayload) -> Result<Vec<Order>> {
let url = self.base_url.join(API_ORDERS)?;
let res = self.delete(url, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
10 changes: 5 additions & 5 deletions client/src/routes/rfq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl BpxClient {
pub async fn submit_rfq(&self, payload: RequestForQuotePayload) -> Result<RequestForQuote> {
let endpoint = self.base_url.join(API_RFQ)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

pub async fn cancel_rfq(
Expand All @@ -38,7 +38,7 @@ impl BpxClient {
) -> Result<RequestForQuote> {
let endpoint = self.base_url.join(API_RFQ_CANCEL)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

pub async fn refresh_rfq(
Expand All @@ -47,19 +47,19 @@ impl BpxClient {
) -> Result<RequestForQuote> {
let endpoint = self.base_url.join(API_RFQ_REFRESH)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

pub async fn accept_quote(&self, payload: QuoteAcceptPayload) -> Result<RequestForQuote> {
let endpoint = self.base_url.join(API_RFQ_ACCEPT)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

pub async fn submit_quote(&self, payload: QuotePayload) -> Result<Quote> {
let endpoint = self.base_url.join(API_RFQ_QUOTE)?;
let res = self.post(endpoint, payload).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

#[cfg(feature = "ws")]
Expand Down
4 changes: 2 additions & 2 deletions client/src/routes/trades.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}

/// Fetches historical trades for a given symbol, with optional limit and offset.
Expand All @@ -40,6 +40,6 @@ impl BpxClient {
}
}
let res = self.get(url).await?;
res.json().await.map_err(Into::into)
Self::json_with_context(res).await
}
}
4 changes: 1 addition & 3 deletions client/src/routes/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ impl BpxClient {
) -> Result<RequestTwoFactorResponse> {
let endpoint = self.base_url.join(API_USER_2FA)?;
let res = self.post(endpoint, payload).await?;

let data: RequestTwoFactorResponse = res.json().await?;
Ok(data)
Self::json_with_context(res).await
}
}
1 change: 1 addition & 0 deletions types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub enum Blockchain {
Optimism,
Aptos,
Sei,
Stable,
Tron,
#[strum(serialize = "0G")]
#[serde(rename = "0G")]
Expand Down
Loading