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
146 changes: 108 additions & 38 deletions src/exchange/exchange_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
},
helpers::{next_nonce, uuid_to_hex_string},
info::info_client::InfoClient,
market_meta_store::MarketMetaStore,
meta::Meta,
prelude::*,
req::HttpClient,
Expand Down Expand Up @@ -110,22 +111,16 @@ impl ExchangeClient {
let client = client.unwrap_or_default();
let base_url = base_url.unwrap_or(BaseUrl::Mainnet);

let info = InfoClient::new(None, Some(base_url)).await?;
let info = InfoClient::new(Some(client.clone()), Some(base_url)).await?;
let meta = if let Some(meta) = meta {
meta
} else {
info.meta().await?
};

let mut coin_to_asset = HashMap::new();
for (asset_ind, asset) in meta.universe.iter().enumerate() {
coin_to_asset.insert(asset.name.clone(), asset_ind as u32);
}

coin_to_asset = info
.spot_meta()
.await?
.add_pair_and_name_to_index_map(coin_to_asset);
let spot_meta = info.spot_meta().await?;
let market_meta_data = MarketMetaStore::build_data(&meta, &spot_meta, &[]);
let coin_to_asset = market_meta_data.coin_to_asset.clone();

Ok(ExchangeClient {
wallet,
Expand All @@ -139,6 +134,17 @@ impl ExchangeClient {
})
}

fn asset_id(&self, coin: &str) -> Result<u32> {
self.coin_to_asset
.get(coin)
.copied()
.ok_or(Error::AssetNotFound)
}

fn conversion_map(&self) -> HashMap<String, u32> {
self.coin_to_asset.clone()
}

async fn post(
&self,
action: serde_json::Value,
Expand Down Expand Up @@ -420,28 +426,28 @@ impl ExchangeClient {
_ => return Err(Error::GenericRequest("Invalid base URL".to_string())),
};
let info_client = InfoClient::new(None, Some(base_url)).await?;
let meta = info_client.meta().await?;

let asset_meta = meta
.universe
.iter()
.find(|a| a.name == asset)
let spot_meta = info_client.spot_meta().await?;
let market_meta_data = MarketMetaStore::build_data(&self.meta, &spot_meta, &[]);
let market_meta_store = MarketMetaStore::from_data(market_meta_data);
let asset_id = market_meta_store
.asset_id(asset)
.or_else(|| self.coin_to_asset.get(asset).copied())
.ok_or(Error::AssetNotFound)?;

let sz_decimals = asset_meta.sz_decimals;
let max_decimals: u32 = if self.coin_to_asset[asset] < 10000 {
6
} else {
8
};
let sz_decimals = market_meta_store
.sz_decimals(asset)
.ok_or(Error::AssetNotFound)?;
let max_decimals: u32 = if asset_id < 10000 { 6 } else { 8 };
let price_decimals = max_decimals.saturating_sub(sz_decimals);

let px = if let Some(px) = px {
px
} else {
let all_mids = info_client.all_mids().await?;
let mid_key = market_meta_store
.mid_key(asset)
.unwrap_or_else(|| asset.to_string());
all_mids
.get(asset)
.get(&mid_key)
.ok_or(Error::AssetNotFound)?
.parse::<f64>()
.map_err(|_| Error::FloatStringParse)?
Expand Down Expand Up @@ -488,10 +494,11 @@ impl ExchangeClient {
let wallet = wallet.unwrap_or(&self.wallet);
let timestamp = next_nonce();

let coin_to_asset = self.conversion_map();
let mut transformed_orders = Vec::new();

for order in orders {
transformed_orders.push(order.convert(&self.coin_to_asset)?);
transformed_orders.push(order.convert(&coin_to_asset)?);
}

let action = Actions::Order(BulkOrder {
Expand All @@ -518,10 +525,11 @@ impl ExchangeClient {

builder.builder = builder.builder.to_lowercase();

let coin_to_asset = self.conversion_map();
let mut transformed_orders = Vec::new();

for order in orders {
transformed_orders.push(order.convert(&self.coin_to_asset)?);
transformed_orders.push(order.convert(&coin_to_asset)?);
}

let action = Actions::Order(BulkOrder {
Expand Down Expand Up @@ -555,10 +563,7 @@ impl ExchangeClient {

let mut transformed_cancels = Vec::new();
for cancel in cancels.into_iter() {
let &asset = self
.coin_to_asset
.get(&cancel.asset)
.ok_or(Error::AssetNotFound)?;
let asset = self.asset_id(&cancel.asset)?;
transformed_cancels.push(CancelRequest {
asset,
oid: cancel.oid,
Expand Down Expand Up @@ -593,11 +598,12 @@ impl ExchangeClient {
let wallet = wallet.unwrap_or(&self.wallet);
let timestamp = next_nonce();

let coin_to_asset = self.conversion_map();
let mut transformed_modifies = Vec::new();
for modify in modifies.into_iter() {
transformed_modifies.push(ModifyRequest {
oid: modify.oid,
order: modify.order.convert(&self.coin_to_asset)?,
order: modify.order.convert(&coin_to_asset)?,
});
}

Expand Down Expand Up @@ -631,10 +637,7 @@ impl ExchangeClient {

let mut transformed_cancels: Vec<CancelRequestCloid> = Vec::new();
for cancel in cancels.into_iter() {
let &asset = self
.coin_to_asset
.get(&cancel.asset)
.ok_or(Error::AssetNotFound)?;
let asset = self.asset_id(&cancel.asset)?;
transformed_cancels.push(CancelRequestCloid {
asset,
cloid: uuid_to_hex_string(cancel.cloid),
Expand Down Expand Up @@ -664,7 +667,7 @@ impl ExchangeClient {

let timestamp = next_nonce();

let &asset_index = self.coin_to_asset.get(coin).ok_or(Error::AssetNotFound)?;
let asset_index = self.asset_id(coin)?;
let action = Actions::UpdateLeverage(UpdateLeverage {
asset: asset_index,
is_cross,
Expand All @@ -689,7 +692,7 @@ impl ExchangeClient {
let amount = (amount * 1_000_000.0).round() as i64;
let timestamp = next_nonce();

let &asset_index = self.coin_to_asset.get(coin).ok_or(Error::AssetNotFound)?;
let asset_index = self.asset_id(coin)?;
let action = Actions::UpdateIsolatedMargin(UpdateIsolatedMargin {
asset: asset_index,
is_buy: true,
Expand Down Expand Up @@ -884,13 +887,14 @@ fn round_to_significant_and_decimal(value: f64, sig_figs: u32, max_decimals: u32

#[cfg(test)]
mod tests {
use std::str::FromStr;
use std::{collections::HashMap, str::FromStr};

use alloy::primitives::address;

use super::*;
use crate::{
exchange::order::{Limit, OrderRequest, Trigger},
exchange::{cancel::ClientCancelRequest, modify::ClientModifyRequest},
Order,
};

Expand Down Expand Up @@ -1164,4 +1168,70 @@ mod tests {

Ok(())
}

fn test_exchange_client(coin_to_asset: HashMap<String, u32>) -> Result<ExchangeClient> {
let wallet = get_wallet()?;
Ok(ExchangeClient {
http_client: HttpClient {
client: Client::new(),
base_url: BaseUrl::Mainnet.get_url(),
},
wallet,
meta: Meta { universe: vec![] },
vault_address: None,
coin_to_asset,
})
}

#[test]
fn exchange_client_conversion_uses_market_meta_store() -> Result<()> {
let coin_to_asset = HashMap::from([
("BTC".to_string(), 0),
("HYPE/USDC".to_string(), 10107),
("@107".to_string(), 10107),
("dexA:AAA".to_string(), 110000),
]);
let client = test_exchange_client(coin_to_asset)?;

let order = ClientOrderRequest {
asset: "HYPE/USDC".to_string(),
is_buy: true,
reduce_only: false,
limit_px: 1.25,
sz: 2.0,
cloid: None,
order_type: ClientOrder::Limit(ClientLimit {
tif: "Gtc".to_string(),
}),
}
.convert(&client.conversion_map())?;
assert_eq!(order.asset, 10107);

let modify = ClientModifyRequest {
oid: 7,
order: ClientOrderRequest {
asset: "dexA:AAA".to_string(),
is_buy: false,
reduce_only: false,
limit_px: 2.0,
sz: 3.0,
cloid: None,
order_type: ClientOrder::Limit(ClientLimit {
tif: "Alo".to_string(),
}),
},
};
assert_eq!(
modify.order.convert(&client.conversion_map())?.asset,
110000
);

let cancel = ClientCancelRequest {
asset: "@107".to_string(),
oid: 42,
};
assert_eq!(client.asset_id(&cancel.asset)?, 10107);

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub fn bps_diff(x: f64, y: f64) -> u16 {
}
}

#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum BaseUrl {
Localhost,
Testnet,
Expand Down
38 changes: 37 additions & 1 deletion src/info/info_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
L2SnapshotResponse, OpenOrdersResponse, OrderInfo, RecentTradesResponse, UserFillsResponse,
UserStateResponse,
},
meta::{AssetContext, Meta, SpotMeta, SpotMetaAndAssetCtxs},
meta::{AssetContext, Meta, PerpDex, SpotMeta, SpotMetaAndAssetCtxs},
prelude::*,
req::HttpClient,
ws::{Subscription, WsManager},
Expand Down Expand Up @@ -58,6 +58,7 @@ pub enum InfoRequest {
MetaAndAssetCtxs,
SpotMeta,
SpotMetaAndAssetCtxs,
PerpDexs,
AllMids,
UserFills {
user: Address,
Expand Down Expand Up @@ -182,6 +183,17 @@ impl InfoClient {
serde_json::from_str(&return_data).map_err(|e| Error::JsonParse(e.to_string()))
}

async fn send_info_value<T: for<'a> Deserialize<'a>>(
&self,
info_request: serde_json::Value,
) -> Result<T> {
let data =
serde_json::to_string(&info_request).map_err(|e| Error::JsonParse(e.to_string()))?;

let return_data = self.http_client.post("/info", data).await?;
serde_json::from_str(&return_data).map_err(|e| Error::JsonParse(e.to_string()))
}

pub async fn open_orders(&self, address: Address) -> Result<Vec<OpenOrdersResponse>> {
let input = InfoRequest::OpenOrders { user: address };
self.send_info_request(input).await
Expand Down Expand Up @@ -212,6 +224,19 @@ impl InfoClient {
self.send_info_request(input).await
}

pub async fn meta_for_dex(&self, dex: String) -> Result<Meta> {
self.send_info_value(serde_json::json!({
"type": "meta",
"dex": dex,
}))
.await
}

pub async fn perp_dexs(&self) -> Result<Vec<Option<PerpDex>>> {
let input = InfoRequest::PerpDexs;
self.send_info_request(input).await
}

pub async fn meta_and_asset_contexts(&self) -> Result<(Meta, Vec<AssetContext>)> {
let input = InfoRequest::MetaAndAssetCtxs;
self.send_info_request(input).await
Expand Down Expand Up @@ -321,3 +346,14 @@ impl InfoClient {
self.send_info_request(input).await
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn serializes_perp_dexs_request() {
let json = serde_json::to_value(InfoRequest::PerpDexs).unwrap();
assert_eq!(json, serde_json::json!({ "type": "perpDexs" }));
}
}
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod exchange;
mod helpers;
mod info;
mod market_maker;
mod market_meta_store;
mod meta;
mod prelude;
mod req;
Expand All @@ -18,5 +19,8 @@ pub use exchange::*;
pub use helpers::{bps_diff, truncate_float, BaseUrl};
pub use info::{info_client::*, *};
pub use market_maker::{MarketMaker, MarketMakerInput, MarketMakerRestingOrder};
pub use meta::{AssetContext, AssetMeta, Meta, MetaAndAssetCtxs, SpotAssetMeta, SpotMeta};
pub use market_meta_store::{
MarketMetaStore, MarketMetaStoreData, MarketMetaStoreDexs, MarketMetaStoreOptions,
};
pub use meta::{AssetContext, AssetMeta, Meta, MetaAndAssetCtxs, PerpDex, SpotAssetMeta, SpotMeta};
pub use ws::*;
Loading