Skip to main content

df_hyperliquid_core/transfer/
erc20.rs

1//! The ERC-20 calls a bridge or a Core link needs: `transfer` (USDC into the
2//! Arbitrum bridge, a token back to Core through its system address),
3//! `balanceOf`, `approve`, `decimals` and `symbol`, and their answers.
4
5use crate::client::evm::abi::{self, AbiError, Token};
6use crate::client::evm::u256::{B256, U256};
7use crate::utils::Address;
8
9/// `transfer(address,uint256)`.
10pub const TRANSFER: [u8; 4] = [0xa9, 0x05, 0x9c, 0xbb];
11/// `balanceOf(address)`.
12pub const BALANCE_OF: [u8; 4] = [0x70, 0xa0, 0x82, 0x31];
13/// `approve(address,uint256)`.
14pub const APPROVE: [u8; 4] = [0x09, 0x5e, 0xa7, 0xb3];
15/// `decimals()`.
16pub const DECIMALS: [u8; 4] = [0x31, 0x3c, 0xe5, 0x67];
17/// `symbol()`.
18pub const SYMBOL: [u8; 4] = [0x95, 0xd8, 0x9b, 0x41];
19
20fn call(selector: [u8; 4], words: &[[u8; 32]]) -> Vec<u8> {
21    let mut out = selector.to_vec();
22    for word in words {
23        out.extend_from_slice(word);
24    }
25    out
26}
27
28/// `transfer(to, amount)`, the amount in the token's own units.
29#[must_use]
30pub fn transfer(to: Address, amount: U256) -> Vec<u8> {
31    call(TRANSFER, &[B256::from_address(to).0, amount.to_be_bytes()])
32}
33
34/// `balanceOf(owner)`; its answer reads with [`read_amount`].
35#[must_use]
36pub fn balance_of(owner: Address) -> Vec<u8> {
37    call(BALANCE_OF, &[B256::from_address(owner).0])
38}
39
40/// `approve(spender, amount)`.
41#[must_use]
42pub fn approve(spender: Address, amount: U256) -> Vec<u8> {
43    call(APPROVE, &[B256::from_address(spender).0, amount.to_be_bytes()])
44}
45
46/// `decimals()`; its answer reads with [`read_decimals`].
47#[must_use]
48pub fn decimals() -> Vec<u8> {
49    DECIMALS.to_vec()
50}
51
52/// `symbol()`; its answer reads with [`read_symbol`].
53#[must_use]
54pub fn symbol() -> Vec<u8> {
55    SYMBOL.to_vec()
56}
57
58fn one(shape: &str, answer: &[u8]) -> Result<Token, AbiError> {
59    abi::decode(&abi::params(shape)?, answer)?.into_iter().next().ok_or(AbiError::Short(answer.len()))
60}
61
62/// A `uint256` answer: a balance or an allowance.
63pub fn read_amount(answer: &[u8]) -> Result<U256, AbiError> {
64    one("uint256", answer)?.as_u256().ok_or(AbiError::Short(answer.len()))
65}
66
67/// The `uint8` `decimals()` answers.
68pub fn read_decimals(answer: &[u8]) -> Result<u8, AbiError> {
69    one("uint8", answer)?.as_u8().ok_or(AbiError::Short(answer.len()))
70}
71
72/// The `string` `symbol()` answers.
73pub fn read_symbol(answer: &[u8]) -> Result<String, AbiError> {
74    match one("string", answer)? {
75        Token::String(text) => Ok(text),
76        _ => Err(AbiError::Short(answer.len())),
77    }
78}
79
80#[cfg(test)]
81#[path = "tests/erc20.rs"]
82mod tests;