#![allow(unused_imports, clippy::too_many_lines)]
use alloy::{hex, signers::local::LocalSigner};
use anyhow::anyhow;
use clap::{Parser, Subcommand};
use rhyperliquid::{
cli::{Cli, Commands},
init_tracing::init_tracing,
types::{info::user::CandleSnapshotRequest, ws::SubscriptionResponse},
HyperliquidClientBuilder,
};
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing();
let cli = Cli::parse();
#[allow(clippy::expect_used)]
let mut hyperliquid = &mut HyperliquidClientBuilder::new();
if let Some(network) = cli.network {
let network: String = network;
match network.to_lowercase().as_str() {
"testnet" => {
hyperliquid = hyperliquid.testnet();
}
"mainnet" => {
hyperliquid = hyperliquid.mainnet();
}
_ => hyperliquid = hyperliquid.testnet(),
}
} else {
hyperliquid = hyperliquid.testnet();
}
hyperliquid = hyperliquid.with_subscriptions();
if let Some(signer_permission) = cli.allow_signer_key_env {
if signer_permission {
let signer_str = std::env::var("HL_PRIVATE_KEY")?;
let key_hex = signer_str.strip_prefix("0x").unwrap_or(&signer_str);
let key_bytes = hex::decode(key_hex)?;
hyperliquid = hyperliquid.with_wallet(LocalSigner::from_slice(&key_bytes)?);
}
}
let client = hyperliquid.build()?;
let info_api = &client.info();
let exchange_api = &client.exchange();
let mut subs = client.subscriptions().await?;
match cli.command {
Commands::AllMids { dex } => {
let all_mids = info_api.all_mids(dex).await?;
tracing::info!("{:?}", all_mids);
return Ok(());
}
Commands::OpenOrders { user, dex } => {
let open_orders = info_api.open_orders(&user, dex.as_deref()).await?;
tracing::info!("{:?}", open_orders);
return Ok(());
}
Commands::FrontendOpenOrders { user, dex } => {
let frontend_open_orders = info_api
.open_orders_with_additional_info(&user, dex.as_deref())
.await?;
tracing::info!("{:?}", frontend_open_orders);
return Ok(());
}
Commands::UserFills {
user,
aggregate_by_time,
} => {
let user_fills = info_api.fills(&user, aggregate_by_time).await?;
tracing::info!({"{:?}", user_fills});
return Ok(());
}
Commands::UserFillsByTime {
user,
start_time,
end_time,
aggregate_by_time,
} => {
let fills_by_time = info_api
.fills_by_time(&user, start_time, end_time, aggregate_by_time)
.await?;
tracing::info!("{:?}", fills_by_time);
return Ok(());
}
Commands::UserRateLimit { user } => {
let user_rate_limit = info_api.rate_limits(&user).await?;
tracing::info!("{:?}", user_rate_limit);
return Ok(());
}
Commands::OrderStatus { user, oid } => {
let order_status = info_api
.order_status(&user, rhyperliquid::types::info::OrderId::Numeric(oid))
.await?;
tracing::info!("{:?}", order_status);
return Ok(());
}
Commands::L2Book {
coin,
n_sig_figs,
mantissa,
} => {
let l2_book = info_api
.l2_book_snapshot(&coin, n_sig_figs, mantissa)
.await?;
tracing::info!("{:?}", l2_book);
return Ok(());
}
Commands::CandleSnapshot {
coin,
interval,
start_time,
end_time,
} => {
let snapshot = info_api
.candle_snapshot(CandleSnapshotRequest {
coin,
interval,
start_time,
end_time,
})
.await?;
tracing::info!("{:?}", snapshot);
return Ok(());
}
Commands::HistoricalOrders { user } => {
let historical_orders = info_api.historical_orders(&user).await?;
tracing::info!("{:?}", historical_orders);
return Ok(());
}
Commands::SubAccounts { user } => {
let sub_accounts = info_api.subaccounts(&user).await?;
if let Some(accounts) = sub_accounts {
tracing::info!("{:?}", accounts);
} else {
tracing::info!("User {:?} does not have subaccounts.", user);
}
return Ok(());
}
Commands::VaultDetails {
vault_address,
user,
} => {
let vault_details = info_api
.vault_details(&vault_address, user.as_deref())
.await?;
tracing::info!("{:?}", vault_details);
return Ok(());
}
Commands::UserVaultEquities { user } => {
let equities = info_api.vault_deposits(&user).await?;
tracing::info!("{:?}", equities);
return Ok(());
}
Commands::UserRole { user } => {
let role = info_api.role(&user).await?;
tracing::info!("{:?}", role);
return Ok(());
}
Commands::Portfolio { user } => {
let portfolio = info_api.portfolio(&user).await?;
tracing::info!("{:?}", portfolio);
return Ok(());
}
Commands::Referral { user } => {
let referral = info_api.portfolio(&user).await?;
tracing::info!("{:?}", referral);
return Ok(());
}
Commands::UserFees { user } => {
let user_fees = info_api.fees(&user).await?;
tracing::info!("{:?}", user_fees);
return Ok(());
}
Commands::Order(cmd_args) => {
let meta = info_api.spot_metadata().await?;
let asset_idx = meta
.tokens
.iter()
.position(|asset| asset.name == cmd_args.coin())
.ok_or_else(|| anyhow!("Unknown coin: {}", cmd_args.coin()))?;
let (order, grouping, builder, vault, expires) =
cmd_args.build(u32::try_from(asset_idx).expect("index conversion to fit into u32"));
exchange_api
.place_order(order, grouping, builder, vault, expires)
.await?;
}
Commands::Cancel(cmd_args) => {
let meta = info_api.spot_metadata().await?;
let asset_idx = meta
.tokens
.iter()
.position(|asset| asset.name == cmd_args.coin())
.ok_or_else(|| anyhow!("Unknown coin: {}", cmd_args.coin()))?;
let (cancel_req, vault_address, expires_after) =
cmd_args.build(u32::try_from(asset_idx).expect("index conversion to fit into u32"));
exchange_api
.cancel_order(cancel_req, vault_address, expires_after)
.await?;
}
Commands::SubscribeOpenOrders { user } => {
subs.subscribe_open_orders(user).await?;
}
Commands::SubscribeAllMids { dex } => {
subs.subscribe_all_mids(dex).await?;
}
Commands::SubscribeL2Book {
coin,
n_sig_figs,
mantissa,
} => {
subs.subscribe_l2_book(coin, n_sig_figs, mantissa).await?;
}
Commands::SubscribeCandleSnapshot { coin, interval } => {
subs.subscribe_candle_snapshot(coin, interval).await?;
}
Commands::SubscribeNotifications { user } => {
subs.subscribe_notifications(user).await?;
}
Commands::SubscribeWebData3 { user } => {
subs.subscribe_webdata3(user).await?;
}
Commands::SubscribeTwapStates { user } => {
subs.subscribe_twap_states(user).await?;
}
Commands::SubscribeClearinghouseState { user } => {
subs.subscribe_clearinghouse_state(user).await?;
}
Commands::SubscribeUserEvents { user } => {
subs.subscribe_user_events(user).await?;
}
Commands::SubscribeUserFills { user } => {
subs.subscribe_user_fills(user).await?;
}
Commands::SubscribeUserFunding { user } => {
subs.subscribe_user_funding(user).await?;
}
Commands::SubscribeUserNonFundingLedgerUpdates { user } => {
subs.subscribe_user_non_funding_ledger_updates(user).await?;
}
Commands::SubscribeActiveAssetCtx { coin } => {
subs.subscribe_active_asset_ctx(coin).await?;
}
Commands::SubscribeActiveAssetData { user, coin } => {
subs.subscribe_active_asset_data(user, coin).await?;
}
Commands::SubscribeUserTwapSliceFills { user } => {
subs.subscribe_user_twap_slice_fills(user).await?;
}
Commands::SubscribeUserTwapHistory { user } => {
subs.subscribe_user_twap_history(user).await?;
}
Commands::SubscribeBbo { user } => {
subs.subscribe_bbo(user).await?;
}
}
let mut events = subs.events;
loop {
match events.recv().await? {
SubscriptionResponse::AllMids(ws_all_mids) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_all_mids)?);
}
SubscriptionResponse::L2Book(ws_l2_book) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_l2_book)?);
}
SubscriptionResponse::Candle(ws_candle) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_candle)?);
}
SubscriptionResponse::Notification(ws_notification) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_notification)?);
}
SubscriptionResponse::WebData3(ws_webdata3) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_webdata3)?);
}
SubscriptionResponse::TwapStates(ws_twap_states) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_twap_states)?);
}
SubscriptionResponse::ClearinghouseState(ws_clearinghouse_state) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_clearinghouse_state)?);
}
SubscriptionResponse::OpenOrders(ws_open_orders) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_open_orders)?);
}
SubscriptionResponse::UserEvents(ws_user_events) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_user_events)?);
}
SubscriptionResponse::UserFills(ws_fills) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_fills)?);
}
SubscriptionResponse::UserFundings(ws_fundings) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_fundings)?);
}
SubscriptionResponse::UserNonFundingLedgerUpdates(
ws_user_non_funding_ledger_updates,
) => {
tracing::info!(
"{}",
serde_json::to_string_pretty(&ws_user_non_funding_ledger_updates)?
);
}
SubscriptionResponse::ActiveAssetCtx(ws_active_asset_ctx) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_active_asset_ctx)?);
}
SubscriptionResponse::ActiveAssetData(ws_active_asset_data) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_active_asset_data)?);
}
SubscriptionResponse::UserTwapSliceFills(ws_user_twap_slice_fills) => {
tracing::info!(
"{}",
serde_json::to_string_pretty(&ws_user_twap_slice_fills)?
);
}
SubscriptionResponse::UserTwapHistory(ws_user_twap_history) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_user_twap_history)?);
}
SubscriptionResponse::Bbo(ws_bbo) => {
tracing::info!("{}", serde_json::to_string_pretty(&ws_bbo)?);
}
SubscriptionResponse::Error(ws_error_response) => {
tracing::info!("{:?}", serde_json::to_string_pretty(&ws_error_response));
}
SubscriptionResponse::SubscriptionResponse(subscription_confirmation) => {
tracing::info!("Subscription confirmed {:?}", subscription_confirmation);
}
SubscriptionResponse::Pong => {
tracing::info!("Received `pong` response from the server");
}
SubscriptionResponse::Trades(ws_trades) => {
tracing::info!("{:?}", ws_trades);
}
}
}
}