Skip to main content

df_hyperliquid_core/transfer/send/
token.rs

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