mod accounts;
mod market;
mod market_stream;
mod order;
mod stream;
use accounts::Accounts;
use market::Market;
use order::Order;
pub use market_stream::{MarketStream, MarketStreamEvent, MarketStreamHandle};
pub use stream::{AccountStream, ReconnectConfig, StreamError, StreamEvent, StreamHandle};
use reqwest::RequestBuilder;
use serde::{Deserialize, Serialize};
use whisky::{decrypt_with_cipher, WError, Wallet, WalletType};
use crate::{
order::{CancelAllOrdersResponse, CancelOrderResponse, SubmitPlaceOrderTransactionResponse},
OrderSide, OrderType,
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Stage {
Mainnet,
Staging,
Custom(String, String),
}
pub struct DeltaDeFi {
pub accounts: Accounts,
pub market: Market,
pub order: Order,
pub stream: AccountStream,
pub market_stream: MarketStream,
pub master_wallet: Option<Wallet>,
pub operation_wallet: Option<Wallet>,
}
impl DeltaDeFi {
pub fn new(
api_key: String,
network: Stage,
master_key: Option<WalletType>,
) -> Result<Self, WError> {
let master_wallet = match master_key {
Some(key) => Some(Wallet::new(key).map_err(WError::from_err("DeltaDeFi - new"))?),
None => None,
};
let api = Api::new(api_key, network);
Ok(DeltaDeFi {
accounts: Accounts::new(api.clone()),
market: Market::new(api.clone()),
order: Order::new(api.clone()),
stream: AccountStream::new(api.ws_url.clone(), api.api_key.clone()),
market_stream: MarketStream::new(api.ws_url.clone()),
master_wallet,
operation_wallet: None,
})
}
pub async fn load_operation_key(&mut self, password: &str) -> Result<(), WError> {
let res = self
.accounts
.get_operation_key()
.await
.map_err(WError::from_err("DeltaDeFi - load_operation_key"))?;
let operation_key = decrypt_with_cipher(&res.encrypted_operation_key, password).map_err(
WError::from_err("DeltaDeFi - load_operation_key - decrypt_with_cipher"),
)?;
let operation_wallet = Wallet::new_root_key(&operation_key).map_err(WError::from_err(
"DeltaDeFi - load_operation_key - create operation wallet",
))?;
self.operation_wallet = Some(operation_wallet);
Ok(())
}
pub fn sign_tx_by_master_key(&self, tx: &str) -> Result<String, WError> {
if let Some(wallet) = &self.master_wallet {
wallet.sign_tx(tx)
} else {
Err(WError::new("DeltaDeFi", "No wallet found"))
}
}
pub fn sign_tx_by_operation_key(&self, tx: &str) -> Result<String, WError> {
if let Some(wallet) = &self.operation_wallet {
wallet.sign_tx(tx)
} else {
Err(WError::new("DeltaDeFi", "No wallet found"))
}
}
pub async fn post_order(
&self,
symbol: &str,
side: OrderSide,
order_type: OrderType,
base_quantity: Option<String>,
quote_quantity: Option<String>,
price: Option<String>,
max_slippage_basis_point: Option<String>,
post_only: Option<bool>,
) -> Result<SubmitPlaceOrderTransactionResponse, WError> {
let build_res = self
.order
.build_place_order_transaction(
symbol,
side,
order_type,
base_quantity,
quote_quantity,
price,
max_slippage_basis_point,
post_only,
)
.await?;
let signed_tx = self.sign_tx_by_operation_key(&build_res.tx_hex)?;
self.order
.submit_place_order_transaction(&build_res.order_id, &signed_tx)
.await
}
pub async fn cancel_order(&self, order_id: &str) -> Result<CancelOrderResponse, WError> {
self.order.cancel_order(order_id).await
}
pub async fn cancel_all_orders(
&self,
symbol: &str,
) -> Result<CancelAllOrdersResponse, WError> {
self.order.cancel_all_orders(symbol).await
}
pub async fn subscribe_account_stream(
&self,
buffer_size: Option<usize>,
) -> Result<(StreamHandle, tokio::sync::mpsc::Receiver<crate::responses::stream::StreamMessage>), WError> {
self.stream.subscribe(buffer_size).await
}
}
#[derive(Clone)]
pub struct Api {
pub base_url: String,
pub ws_url: String,
pub api_key: String,
pub network: Stage,
pub http_client: reqwest::Client,
}
impl Api {
pub fn new(api_key: String, network: Stage) -> Self {
let (base_url, ws_url) = match &network {
Stage::Mainnet => (
"https://api.deltadefi.io/".to_string(),
"wss://stream.deltadefi.io".to_string(),
),
Stage::Staging => (
"https://api-staging.deltadefi.io".to_string(),
"wss://stream-staging.deltadefi.io".to_string(),
),
Stage::Custom(url, ws_url) => (url.to_string(), ws_url.to_string()),
};
let http_client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300))
.build()
.expect("Failed to create HTTP client");
Api {
api_key,
network,
base_url,
ws_url,
http_client,
}
}
async fn send_request(
&self,
req: RequestBuilder,
response_body: &mut String,
) -> Result<(), Box<dyn std::error::Error>> {
let req = req
.header("Accept", "application/json")
.header("X-API-KEY", &self.api_key)
.build()?;
let method = req.method().to_string();
let url = req.url().to_string();
let response = self.http_client.execute(req).await?;
let status = response.status();
if status.is_success() {
*response_body = response.text().await?;
Ok(())
} else {
let error_body = response.text().await.unwrap_or_else(|_| "<no body>".to_string());
Err(format!(
"HTTP {} {} -> {} | Response: {}",
method, url, status, error_body
).into())
}
}
pub async fn get(&self, url: &str) -> Result<String, WError> {
let req = self.http_client.get(format!("{}{}", &self.base_url, url));
let mut response_body = String::new();
self.send_request(req, &mut response_body)
.await
.map_err(WError::from_err("DeltaDeFi - get - send_request"))?;
Ok(response_body)
}
pub async fn get_with_params<T: Serialize>(
&self,
url: &str,
params: &T,
) -> Result<String, WError> {
let req = self
.http_client
.get(format!("{}{}", &self.base_url, url))
.query(params);
let mut response_body = String::new();
self.send_request(req, &mut response_body)
.await
.map_err(WError::from_err(
"DeltaDeFi - get_with_params - send_request",
))?;
Ok(response_body)
}
pub async fn post<T: Serialize>(&self, url: &str, body: T) -> Result<String, WError> {
let json_body = serde_json::to_string(&body)
.map_err(WError::from_err("DeltaDeFi - post - json_body"))?;
let req = self
.http_client
.post(format!("{}{}", &self.base_url, url))
.header("Content-Type", "application/json")
.body(json_body.clone());
let mut response_body = String::new();
self.send_request(req, &mut response_body)
.await
.map_err(|e| {
WError::new(
"DeltaDeFi - post - send_request",
&format!("{} | Request body: {}", e, json_body),
)
})?;
Ok(response_body)
}
pub async fn delete<T: Serialize>(&self, url: &str, body: T) -> Result<String, WError> {
let json_body = serde_json::to_string(&body)
.map_err(WError::from_err("DeltaDeFi - delete - json_body"))?;
let req = self
.http_client
.delete(format!("{}{}", &self.base_url, url))
.header("Content-Type", "application/json")
.body(json_body.clone());
let mut response_body = String::new();
self.send_request(req, &mut response_body)
.await
.map_err(|e| {
WError::new(
"DeltaDeFi - delete - send_request",
&format!("{} | Request body: {}", e, json_body),
)
})?;
Ok(response_body)
}
pub async fn patch<T: Serialize>(&self, url: &str, body: T) -> Result<String, WError> {
let json_body = serde_json::to_string(&body)
.map_err(WError::from_err("DeltaDeFi - patch - json_body"))?;
let req = self
.http_client
.patch(format!("{}{}", &self.base_url, url))
.header("Content-Type", "application/json")
.body(json_body.clone());
let mut response_body = String::new();
self.send_request(req, &mut response_body)
.await
.map_err(|e| {
WError::new(
"DeltaDeFi - patch - send_request",
&format!("{} | Request body: {}", e, json_body),
)
})?;
Ok(response_body)
}
}