Skip to main content

df_hyperliquid_core/transfer/
token.rs

1//! A spot token as a transfer names it, read from the caller's `spotMeta`, and HYPE, whose index
2//! and id differ between the networks.
3
4use crate::client::{Network, Token};
5use crate::transfer::check::{HYPE_SYSTEM, system_address};
6use crate::utils::Address;
7use serde::Deserialize;
8
9/// HYPE on each network.
10pub use crate::client::{MAINNET_HYPE, TESTNET_HYPE};
11
12/// HYPE's decimals on HyperEVM, where it is the chain's own coin.
13pub const HYPE_EVM_DECIMALS: u32 = 18;
14
15/// The network's HYPE: [`Network::hype`].
16#[must_use]
17pub const fn hype(network: Network) -> Token {
18    network.hype()
19}
20
21/// One token of `spotMeta`'s `tokens`, the fields a transfer reads.
22#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct SpotToken {
25    pub name: String,
26    /// What a balance's `token` and `borrowLend` name it by.
27    pub index: u32,
28    /// `0x` and 32 hex digits.
29    pub token_id: String,
30    /// The decimals Core counts it in.
31    pub wei_decimals: u32,
32    /// Its ERC-20 on HyperEVM; `null` for a token with none, and for HYPE on
33    /// mainnet, which is HyperEVM's own coin.
34    #[serde(default)]
35    pub evm_contract: Option<EvmContract>,
36}
37
38/// A token's ERC-20 on HyperEVM. The venue spells this key in snake case.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
40pub struct EvmContract {
41    pub address: Address,
42    /// Its decimals on HyperEVM less `weiDecimals`.
43    #[serde(rename = "evm_extra_wei_decimals")]
44    pub extra_wei_decimals: i32,
45}
46
47impl SpotToken {
48    /// `NAME:0x<tokenId>`, the one way a transfer names a token.
49    #[must_use]
50    pub fn wire(&self) -> String {
51        format!("{}:{}", self.name, self.token_id)
52    }
53
54    /// Whether this is the network's HYPE, by its index there.
55    #[must_use]
56    pub fn is_hype(&self, network: Network) -> bool {
57        self.index == network.hype().index
58    }
59
60    /// Where it crosses from Core to HyperEVM: `0x2222...2222` for HYPE, `0x20` and the index for
61    /// any other token.
62    #[must_use]
63    pub fn system_address(&self, network: Network) -> Address {
64        match self.is_hype(network) {
65            true => HYPE_SYSTEM,
66            false => system_address(u64::from(self.index)),
67        }
68    }
69
70    /// Its decimals on HyperEVM: eighteen for HYPE, `weiDecimals` plus the
71    /// ERC-20's extra for a linked token, `None` for a token with no ERC-20.
72    #[must_use]
73    pub fn evm_decimals(&self, network: Network) -> Option<u32> {
74        if self.is_hype(network) {
75            return Some(HYPE_EVM_DECIMALS);
76        }
77        let link = self.evm_contract?;
78        u32::try_from(i64::from(self.wei_decimals) + i64::from(link.extra_wei_decimals)).ok()
79    }
80
81    /// The most places an amount crossing to HyperEVM may have: the fewer of its Core and HyperEVM
82    /// decimals. `None` when it cannot cross.
83    #[must_use]
84    pub fn crossing_places(&self, network: Network) -> Option<u32> {
85        self.evm_decimals(network).map(|evm| evm.min(self.wei_decimals))
86    }
87}
88
89/// USDC or HYPE from the network itself, with no link to HyperEVM read: HYPE
90/// needs none, and USDC does not cross here.
91impl From<Token> for SpotToken {
92    fn from(token: Token) -> SpotToken {
93        SpotToken {
94            name: token.name.to_owned(),
95            index: token.index,
96            token_id: token.token_id.to_owned(),
97            wei_decimals: token.wei_decimals,
98            evm_contract: None,
99        }
100    }
101}
102
103#[cfg(test)]
104#[path = "tests/token.rs"]
105mod tests;