deltaos_client.rs
1 /// DeltaOS REST API client 2 /// 3 /// Provides HTTP client for DeltaOS DEX, perpetuals, and oracle endpoints 4 5 use anyhow::{Context, Result}; 6 use reqwest::{Client, Response}; 7 use serde::{Deserialize, Serialize}; 8 use std::time::Duration; 9 10 /// DeltaOS REST API client 11 pub struct DeltaOSClient { 12 base_url: String, 13 client: Client, 14 } 15 16 impl DeltaOSClient { 17 /// Create a new DeltaOS client 18 pub fn new(base_url: String) -> Result<Self> { 19 let client = Client::builder() 20 .timeout(Duration::from_secs(30)) 21 .build() 22 .context("Failed to build HTTP client")?; 23 24 Ok(Self { base_url, client }) 25 } 26 27 /// Get the latest block height 28 pub async fn get_latest_block_height(&self) -> Result<u32> { 29 let url = format!("{}/mainnet/block/height/latest", self.base_url); 30 let response = self.client.get(&url).send().await?; 31 self.handle_response::<u32>(response).await 32 } 33 34 /// Get block by height 35 pub async fn get_block(&self, height: u32) -> Result<serde_json::Value> { 36 let url = format!("{}/mainnet/block/{}", self.base_url, height); 37 let response = self.client.get(&url).send().await?; 38 self.handle_response(response).await 39 } 40 41 /// Broadcast a transaction 42 pub async fn broadcast_transaction(&self, tx_bytes: &[u8]) -> Result<String> { 43 let url = format!("{}/mainnet/transaction/broadcast", self.base_url); 44 let response = self.client 45 .post(&url) 46 .header("Content-Type", "application/octet-stream") 47 .body(tx_bytes.to_vec()) 48 .send() 49 .await?; 50 51 self.handle_response::<BroadcastResponse>(response) 52 .await 53 .map(|r| r.transaction_id) 54 } 55 56 /// Get orderbook for a trading pair 57 pub async fn get_orderbook(&self, pair: &str) -> Result<Orderbook> { 58 let url = format!("{}/mainnet/dex/orderbook/{}", self.base_url, pair); 59 let response = self.client.get(&url).send().await?; 60 self.handle_response(response).await 61 } 62 63 /// Submit a DEX order 64 pub async fn submit_order(&self, order: &Order) -> Result<String> { 65 let url = format!("{}/mainnet/dex/order", self.base_url); 66 let response = self.client 67 .post(&url) 68 .json(order) 69 .send() 70 .await?; 71 72 self.handle_response::<OrderResponse>(response) 73 .await 74 .map(|r| r.order_id) 75 } 76 77 /// Cancel a DEX order 78 pub async fn cancel_order(&self, order_id: &str) -> Result<()> { 79 let url = format!("{}/mainnet/dex/order/{}/cancel", self.base_url, order_id); 80 let response = self.client.post(&url).send().await?; 81 self.handle_response::<()>(response).await 82 } 83 84 /// Get open positions for an address 85 pub async fn get_positions(&self, address: &str) -> Result<Vec<Position>> { 86 let url = format!("{}/mainnet/perpetuals/positions/{}", self.base_url, address); 87 let response = self.client.get(&url).send().await?; 88 self.handle_response(response).await 89 } 90 91 /// Open a perpetual position 92 pub async fn open_position(&self, position: &PositionRequest) -> Result<String> { 93 let url = format!("{}/mainnet/perpetuals/open", self.base_url); 94 let response = self.client 95 .post(&url) 96 .json(position) 97 .send() 98 .await?; 99 100 self.handle_response::<PositionResponse>(response) 101 .await 102 .map(|r| r.position_id) 103 } 104 105 /// Close a perpetual position 106 pub async fn close_position(&self, position_id: &str) -> Result<()> { 107 let url = format!("{}/mainnet/perpetuals/close/{}", self.base_url, position_id); 108 let response = self.client.post(&url).send().await?; 109 self.handle_response::<()>(response).await 110 } 111 112 /// Get oracle price for an asset 113 pub async fn get_oracle_price(&self, asset: &str) -> Result<OraclePrice> { 114 let url = format!("{}/mainnet/oracle/price/{}", self.base_url, asset); 115 let response = self.client.get(&url).send().await?; 116 self.handle_response(response).await 117 } 118 119 /// Get mempool transactions 120 pub async fn get_mempool_transactions(&self) -> Result<Vec<String>> { 121 let url = format!("{}/mainnet/memoryPool/transactions", self.base_url); 122 let response = self.client.get(&url).send().await?; 123 self.handle_response(response).await 124 } 125 126 /// Handle response and deserialize 127 async fn handle_response<T: for<'de> Deserialize<'de>>(&self, response: Response) -> Result<T> { 128 let status = response.status(); 129 if !status.is_success() { 130 let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string()); 131 anyhow::bail!("HTTP error {}: {}", status, error_text); 132 } 133 134 let body = response.text().await.context("Failed to read response body")?; 135 serde_json::from_str(&body).context("Failed to deserialize response") 136 } 137 } 138 139 #[derive(Debug, Clone, Serialize, Deserialize)] 140 struct BroadcastResponse { 141 transaction_id: String, 142 } 143 144 #[derive(Debug, Clone, Serialize, Deserialize)] 145 pub struct Orderbook { 146 pub pair: String, 147 pub bids: Vec<OrderLevel>, 148 pub asks: Vec<OrderLevel>, 149 } 150 151 #[derive(Debug, Clone, Serialize, Deserialize)] 152 pub struct OrderLevel { 153 pub price: String, 154 pub quantity: String, 155 } 156 157 #[derive(Debug, Clone, Serialize, Deserialize)] 158 pub struct Order { 159 pub pair: String, 160 pub side: String, // "buy" or "sell" 161 pub order_type: String, // "limit" or "market" 162 pub price: Option<String>, 163 pub quantity: String, 164 } 165 166 #[derive(Debug, Clone, Serialize, Deserialize)] 167 struct OrderResponse { 168 order_id: String, 169 } 170 171 #[derive(Debug, Clone, Serialize, Deserialize)] 172 pub struct Position { 173 pub position_id: String, 174 pub pair: String, 175 pub side: String, // "long" or "short" 176 pub size: String, 177 pub entry_price: String, 178 pub liquidation_price: String, 179 } 180 181 #[derive(Debug, Clone, Serialize, Deserialize)] 182 pub struct PositionRequest { 183 pub pair: String, 184 pub side: String, 185 pub size: String, 186 pub leverage: u32, 187 } 188 189 #[derive(Debug, Clone, Serialize, Deserialize)] 190 struct PositionResponse { 191 position_id: String, 192 } 193 194 #[derive(Debug, Clone, Serialize, Deserialize)] 195 pub struct OraclePrice { 196 pub asset: String, 197 pub price: String, 198 pub timestamp: i64, 199 } 200 201 #[cfg(test)] 202 mod tests { 203 use super::*; 204 205 #[test] 206 fn test_client_creation() { 207 let client = DeltaOSClient::new("http://localhost:3031".to_string()); 208 assert!(client.is_ok()); 209 } 210 }