Complete decentralized exchange client implementation featuring: - AMM swaps (constant product, stable, concentrated liquidity) - Liquidity provision with impermanent loss tracking - Perpetual futures (up to 100x leverage, funding rates, liquidation) - Order books with limit orders (GTC, IOC, FOK, GTD) - Yield farming & staking with reward claiming - Real-time WebSocket subscriptions - Analytics (OHLCV, trade history, volume, TVL) Languages: JS/TS, Python, Go, Rust, Java, Kotlin, Swift, Flutter, C, C++, C#, Ruby
222 lines
7.1 KiB
Rust
222 lines
7.1 KiB
Rust
//! Perpetuals Client
|
|
|
|
use crate::dex::types::*;
|
|
use reqwest::Client;
|
|
use serde_json::json;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::Arc;
|
|
|
|
/// Perpetuals sub-client
|
|
pub struct PerpsClient {
|
|
config: DexConfig,
|
|
client: Client,
|
|
closed: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl PerpsClient {
|
|
pub(crate) fn new(config: DexConfig, client: Client, closed: Arc<AtomicBool>) -> Self {
|
|
Self { config, client, closed }
|
|
}
|
|
|
|
/// List all perpetual markets
|
|
pub async fn list_markets(&self) -> Result<Vec<PerpMarket>, DexError> {
|
|
self.get("/perps/markets").await
|
|
}
|
|
|
|
/// Get a specific perpetual market
|
|
pub async fn get_market(&self, symbol: &str) -> Result<PerpMarket, DexError> {
|
|
self.get(&format!("/perps/markets/{}", symbol)).await
|
|
}
|
|
|
|
/// Open a perpetual position
|
|
pub async fn open_position(&self, params: OpenPositionParams) -> Result<PerpPosition, DexError> {
|
|
let mut body = json!({
|
|
"market": params.market,
|
|
"side": params.side,
|
|
"size": params.size,
|
|
"leverage": params.leverage,
|
|
"order_type": params.order_type,
|
|
"margin_type": params.margin_type,
|
|
"reduce_only": params.reduce_only,
|
|
});
|
|
|
|
if let Some(price) = params.limit_price {
|
|
body["limit_price"] = json!(price);
|
|
}
|
|
if let Some(sl) = params.stop_loss {
|
|
body["stop_loss"] = json!(sl);
|
|
}
|
|
if let Some(tp) = params.take_profit {
|
|
body["take_profit"] = json!(tp);
|
|
}
|
|
|
|
self.post("/perps/positions", body).await
|
|
}
|
|
|
|
/// Close a perpetual position
|
|
pub async fn close_position(&self, params: ClosePositionParams) -> Result<PerpPosition, DexError> {
|
|
let mut body = json!({
|
|
"market": params.market,
|
|
"order_type": params.order_type,
|
|
});
|
|
|
|
if let Some(size) = params.size {
|
|
body["size"] = json!(size);
|
|
}
|
|
if let Some(price) = params.limit_price {
|
|
body["limit_price"] = json!(price);
|
|
}
|
|
|
|
self.post("/perps/positions/close", body).await
|
|
}
|
|
|
|
/// Modify a perpetual position
|
|
pub async fn modify_position(&self, params: ModifyPositionParams) -> Result<PerpPosition, DexError> {
|
|
let mut body = json!({});
|
|
|
|
if let Some(leverage) = params.new_leverage {
|
|
body["new_leverage"] = json!(leverage);
|
|
}
|
|
if let Some(margin) = params.new_margin {
|
|
body["new_margin"] = json!(margin);
|
|
}
|
|
if let Some(sl) = params.new_stop_loss {
|
|
body["new_stop_loss"] = json!(sl);
|
|
}
|
|
if let Some(tp) = params.new_take_profit {
|
|
body["new_take_profit"] = json!(tp);
|
|
}
|
|
|
|
self.post(&format!("/perps/positions/{}/modify", params.position_id), body).await
|
|
}
|
|
|
|
/// Get all open positions
|
|
pub async fn get_positions(&self) -> Result<Vec<PerpPosition>, DexError> {
|
|
self.get("/perps/positions").await
|
|
}
|
|
|
|
/// Get position for a specific market
|
|
pub async fn get_position(&self, market: &str) -> Result<Option<PerpPosition>, DexError> {
|
|
self.get(&format!("/perps/positions/{}", market)).await
|
|
}
|
|
|
|
/// Get all open orders
|
|
pub async fn get_orders(&self) -> Result<Vec<PerpOrder>, DexError> {
|
|
self.get("/perps/orders").await
|
|
}
|
|
|
|
/// Cancel an order
|
|
pub async fn cancel_order(&self, order_id: &str) -> Result<(), DexError> {
|
|
self.delete(&format!("/perps/orders/{}", order_id)).await
|
|
}
|
|
|
|
/// Cancel all orders
|
|
pub async fn cancel_all_orders(&self, market: Option<&str>) -> Result<u32, DexError> {
|
|
let path = match market {
|
|
Some(m) => format!("/perps/orders?market={}", m),
|
|
None => "/perps/orders".to_string(),
|
|
};
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct CancelResult {
|
|
cancelled: u32,
|
|
}
|
|
|
|
let result: CancelResult = self.delete(&path).await?;
|
|
Ok(result.cancelled)
|
|
}
|
|
|
|
/// Get funding payment history
|
|
pub async fn get_funding_history(&self, market: &str, limit: u32) -> Result<Vec<FundingPayment>, DexError> {
|
|
self.get(&format!("/perps/funding/{}?limit={}", market, limit)).await
|
|
}
|
|
|
|
/// Get current funding rate
|
|
pub async fn get_funding_rate(&self, market: &str) -> Result<FundingRateInfo, DexError> {
|
|
self.get(&format!("/perps/funding/{}/current", market)).await
|
|
}
|
|
|
|
// Internal methods
|
|
|
|
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
|
|
use std::sync::atomic::Ordering;
|
|
|
|
if self.closed.load(Ordering::SeqCst) {
|
|
return Err(DexError::ClientClosed);
|
|
}
|
|
|
|
let url = format!("{}{}", self.config.endpoint, path);
|
|
let response = self.client
|
|
.get(&url)
|
|
.header("Content-Type", "application/json")
|
|
.header("Authorization", format!("Bearer {}", self.config.api_key))
|
|
.header("X-SDK-Version", "rust/0.1.0")
|
|
.send()
|
|
.await?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(DexError::Http {
|
|
message: format!("HTTP {}", response.status()),
|
|
code: None,
|
|
status: Some(response.status().as_u16()),
|
|
});
|
|
}
|
|
|
|
response.json().await.map_err(DexError::from)
|
|
}
|
|
|
|
async fn post<T: serde::de::DeserializeOwned>(&self, path: &str, body: serde_json::Value) -> Result<T, DexError> {
|
|
use std::sync::atomic::Ordering;
|
|
|
|
if self.closed.load(Ordering::SeqCst) {
|
|
return Err(DexError::ClientClosed);
|
|
}
|
|
|
|
let url = format!("{}{}", self.config.endpoint, path);
|
|
let response = self.client
|
|
.post(&url)
|
|
.header("Content-Type", "application/json")
|
|
.header("Authorization", format!("Bearer {}", self.config.api_key))
|
|
.header("X-SDK-Version", "rust/0.1.0")
|
|
.json(&body)
|
|
.send()
|
|
.await?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(DexError::Http {
|
|
message: format!("HTTP {}", response.status()),
|
|
code: None,
|
|
status: Some(response.status().as_u16()),
|
|
});
|
|
}
|
|
|
|
response.json().await.map_err(DexError::from)
|
|
}
|
|
|
|
async fn delete<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
|
|
use std::sync::atomic::Ordering;
|
|
|
|
if self.closed.load(Ordering::SeqCst) {
|
|
return Err(DexError::ClientClosed);
|
|
}
|
|
|
|
let url = format!("{}{}", self.config.endpoint, path);
|
|
let response = self.client
|
|
.delete(&url)
|
|
.header("Content-Type", "application/json")
|
|
.header("Authorization", format!("Bearer {}", self.config.api_key))
|
|
.header("X-SDK-Version", "rust/0.1.0")
|
|
.send()
|
|
.await?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(DexError::Http {
|
|
message: format!("HTTP {}", response.status()),
|
|
code: None,
|
|
status: Some(response.status().as_u16()),
|
|
});
|
|
}
|
|
|
|
response.json().await.map_err(DexError::from)
|
|
}
|
|
}
|