Skip to main content

df_hyperliquid_core/client/evm/precompiles/
account.rs

1//! What an account holds on HyperCore: perp positions, spot balances, vault
2//! equity, what it can withdraw, its margin on a dex, and whether it exists.
3
4use crate::client::evm::abi::AbiError;
5use crate::client::evm::precompiles::fields::{Fields, tokens};
6use crate::client::evm::precompiles::read::{Arg, Read, read};
7use crate::utils::Address;
8
9/// One perp position, as [`position`] and [`position2`] answer it.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Position {
12    /// Signed size in the perp's size decimals; negative is short.
13    pub szi: i64,
14    /// Entry notional, USDC at 1e6.
15    pub entry_ntl: u64,
16    pub isolated_raw_usd: i64,
17    pub leverage: u32,
18    pub is_isolated: bool,
19}
20
21fn decode_position(at: u16, answer: &[u8]) -> Result<Position, AbiError> {
22    let tokens = tokens(at, answer)?;
23    let mut fields = Fields::of("position", &tokens)?;
24    Ok(Position {
25        szi: fields.i64()?,
26        entry_ntl: fields.u64()?,
27        isolated_raw_usd: fields.i64()?,
28        leverage: fields.u32()?,
29        is_isolated: fields.bool()?,
30    })
31}
32
33/// `0x800`: one account's position in a perp of the main dex, by a `uint16`
34/// index. [`position2`] takes a `uint32`.
35#[must_use]
36pub fn position(user: Address, perp: u16) -> Read<Position> {
37    read(0x800, &[Arg::Address(user), Arg::Uint(u64::from(perp))], |answer| decode_position(0x800, answer))
38}
39
40/// `0x813`: [`position`] with a `uint32` perp index, a new address rather than
41/// a change to the old one.
42#[must_use]
43pub fn position2(user: Address, perp: u32) -> Read<Position> {
44    read(0x813, &[Arg::Address(user), Arg::Uint(u64::from(perp))], |answer| decode_position(0x813, answer))
45}
46
47/// One token's balance on HyperCore's spot side, in its `weiDecimals`.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct SpotBalance {
50    pub total: u64,
51    /// What open orders hold of it.
52    pub hold: u64,
53    pub entry_ntl: u64,
54}
55
56/// `0x801`: a spot balance. `token` is a token index, or an outcome side's
57/// asset id, `100000000 + outcome * 10 + side`.
58#[must_use]
59pub fn spot_balance(user: Address, token: u64) -> Read<SpotBalance> {
60    read(0x801, &[Arg::Address(user), Arg::Uint(token)], |answer| {
61        let tokens = tokens(0x801, answer)?;
62        let mut fields = Fields::of("spotBalance", &tokens)?;
63        Ok(SpotBalance { total: fields.u64()?, hold: fields.u64()?, entry_ntl: fields.u64()? })
64    })
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct UserVaultEquity {
69    /// USDC at 1e6.
70    pub equity: u64,
71    /// Unix milliseconds.
72    pub locked_until_timestamp: u64,
73}
74
75/// `0x802`: an account's equity in a vault, and when it unlocks.
76#[must_use]
77pub fn user_vault_equity(user: Address, vault: Address) -> Read<UserVaultEquity> {
78    read(0x802, &[Arg::Address(user), Arg::Address(vault)], |answer| {
79        let tokens = tokens(0x802, answer)?;
80        let mut fields = Fields::of("userVaultEquity", &tokens)?;
81        Ok(UserVaultEquity { equity: fields.u64()?, locked_until_timestamp: fields.u64()? })
82    })
83}
84
85/// `0x803`: what the account can withdraw now, USDC at 1e6.
86#[must_use]
87pub fn withdrawable(user: Address) -> Read<u64> {
88    read(0x803, &[Arg::Address(user)], |answer| Fields::of("withdrawable", &tokens(0x803, answer)?)?.u64())
89}
90
91/// An account's margin on one perp dex, USDC at 1e6.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct AccountMarginSummary {
94    pub account_value: i64,
95    pub margin_used: u64,
96    pub ntl_pos: u64,
97    pub raw_usd: i64,
98}
99
100/// `0x80f`: margin on a perp dex, 0 the main one.
101#[must_use]
102pub fn account_margin_summary(perp_dex_index: u32, user: Address) -> Read<AccountMarginSummary> {
103    read(0x80f, &[Arg::Uint(u64::from(perp_dex_index)), Arg::Address(user)], |answer| {
104        let tokens = tokens(0x80f, answer)?;
105        let mut fields = Fields::of("accountMarginSummary", &tokens)?;
106        Ok(AccountMarginSummary {
107            account_value: fields.i64()?,
108            margin_used: fields.u64()?,
109            ntl_pos: fields.u64()?,
110            raw_usd: fields.i64()?,
111        })
112    })
113}
114
115/// `0x810`: whether the address exists on HyperCore at all.
116#[must_use]
117pub fn core_user_exists(user: Address) -> Read<bool> {
118    read(0x810, &[Arg::Address(user)], |answer| Fields::of("coreUserExists", &tokens(0x810, answer)?)?.bool())
119}
120
121#[cfg(test)]
122#[path = "tests/account.rs"]
123mod tests;