#![allow(clippy::all)]
use rust_decimal::prelude::*;
use rhyperliquid::{
example_helpers::load_signer,
init_tracing::init_tracing,
response::ResponseInner,
types::exchange::{Grouping, OrderRequest},
HyperliquidClientBuilder, HyperliquidError,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing();
let pkey_signer = load_signer();
let mut builder = HyperliquidClientBuilder::new();
let hyperliquid = builder.testnet().with_wallet(pkey_signer.clone()).build()?;
let info_api = hyperliquid.info();
let exchange_api = hyperliquid.exchange();
let (perp_meta, asset_ctxs) = info_api.perpetuals_asset_contexts().await?;
let btc_asset_and_idx = perp_meta
.universe
.iter()
.enumerate()
.find(|asset| asset.1.name == "BTC");
let (asset_idx, asset_info) = btc_asset_and_idx.ok_or(HyperliquidError::Internal(
"Missing asset in universe".to_string(),
))?;
let btc_ctx = &asset_ctxs
.get(asset_idx)
.ok_or(HyperliquidError::Internal("Asset not found".to_string()))?;
let mark_price = btc_ctx.mark_px;
tracing::info!("BTC mark price: ${}", mark_price);
let multiplier = Decimal::from_str("1.01")?;
let order_price_decimal = mark_price.saturating_mul(multiplier);
let min_notional = Decimal::from(10);
let min_size =
min_notional
.checked_div(order_price_decimal)
.ok_or(HyperliquidError::InvalidPrice(
"Price cannot be 0".to_string(),
))?;
let sz_decimals = u32::try_from(asset_info.sz_decimals)?;
let size = min_size.round_dp_with_strategy(sz_decimals, RoundingStrategy::AwayFromZero);
tracing::info!("Calculated size: {}", size);
tracing::info!(
"Notional value: {}",
size.saturating_mul(order_price_decimal)
);
tracing::info!("Setting leverage to 5x isolated");
let leverage_response = exchange_api
.update_leverage(u32::try_from(asset_idx)?, false, 5, None, None)
.await?;
match leverage_response.response {
ResponseInner::Error(e) => {
tracing::error!("Failed to update leverage: {:?}", e);
return Ok(());
}
ResponseInner::Ok(_) => {
tracing::info!("Successfully set leverage to 5x isolated");
}
}
tracing::info!("Placing market buy order");
let order_req = OrderRequest::new_market_order(
u32::try_from(asset_idx)?,
asset_info,
true,
order_price_decimal,
size,
);
let order_response = exchange_api
.place_order(order_req, Grouping::Na, None, None, None)
.await?;
match order_response.response {
ResponseInner::Error(e) => {
tracing::error!("Failed to place order: {:?}", e);
return Ok(());
}
ResponseInner::Ok(order_data) => {
tracing::info!("Order placed: {:?}", order_data);
}
}
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
tracing::info!("Adding 5 USDC to isolated margin");
let add_margin_response = exchange_api
.update_isolated_margin(u32::try_from(asset_idx)?, true, 5_000_000, None, None)
.await?;
match add_margin_response.response {
ResponseInner::Error(e) => {
tracing::error!("Failed to add margin: {:?}", e);
}
ResponseInner::Ok(_) => {
tracing::info!("Successfully added 5 USDC margin");
}
}
let wallet_address = pkey_signer.address();
let user_state = info_api
.perpetuals_account_summary(&wallet_address.to_string(), None)
.await?;
tracing::info!("Final account state:");
tracing::info!("Margin summary: {:?}", user_state.margin_summary);
tracing::info!("Asset positions: {:?}", user_state.asset_positions);
Ok(())
}