/ crates / integration / src / deltaos_client.rs
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  }