df_hyperliquid_core/transfer/
bridge.rs1use crate::client::Network;
7use crate::client::evm::rpc::{BlockTag, CallRequest, Query, RpcError, eth};
8use crate::client::evm::u256::NATIVE_DECIMALS;
9use crate::client::evm::u256::U256;
10use crate::transfer::erc20;
11use crate::utils::{Address, Dec, USDC_DECIMALS, hex};
12
13const fn address(text: &str) -> Address {
14 match Address::parse_const(text) {
15 Some(address) => address,
16 None => Address::ZERO,
17 }
18}
19
20pub const MAINNET_BRIDGE: Address = address("0x2df1c51e09aecf9cacb7bc98cb1742757f163df7");
22pub const TESTNET_BRIDGE: Address = address("0x08cfc1B6b2dCF36A1480b99353A354AA8AC56f89");
24
25pub const MAINNET_USDC: Address = address("0xaf88d065e77c8cC2239327C5EDb3A432268e5831");
27pub const TESTNET_USDC: Address = address("0x1baAbB04529D43a73232B713C0FE471f7c7334d5");
29
30pub const MINIMUM_DEPOSIT: Dec = Dec::from_int(5);
32
33#[must_use]
35pub const fn bridge(network: Network) -> Address {
36 match network {
37 Network::Mainnet => MAINNET_BRIDGE,
38 Network::Testnet => TESTNET_BRIDGE,
39 }
40}
41
42#[must_use]
44pub const fn usdc(network: Network) -> Address {
45 match network {
46 Network::Mainnet => MAINNET_USDC,
47 Network::Testnet => TESTNET_USDC,
48 }
49}
50
51#[must_use]
53pub const fn rpc_url(network: Network) -> &'static str {
54 match network {
55 Network::Mainnet => "https://arb1.arbitrum.io/rpc",
56 Network::Testnet => "https://sepolia-rollup.arbitrum.io/rpc",
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
61pub enum BadDeposit {
62 #[error("{0} USDC has more than six decimal places")]
63 TooFine(Dec),
64 #[error("{0} USDC is not a positive amount")]
65 NotPositive(Dec),
66 #[error("{0} USDC is below the bridge's minimum of {MINIMUM_DEPOSIT}; less is lost")]
67 BelowMinimum(Dec),
68 #[error("{0} USDC is too large")]
69 TooLarge(Dec),
70}
71
72pub fn usdc_transfer_calldata(to: Address, amount: Dec) -> Result<String, BadDeposit> {
75 if !amount.is_positive() {
76 return Err(BadDeposit::NotPositive(amount));
77 }
78 if amount.places() > USDC_DECIMALS {
79 return Err(BadDeposit::TooFine(amount));
80 }
81 let units = U256::from_dec(amount, USDC_DECIMALS).map_err(|_| BadDeposit::TooLarge(amount))?;
82 Ok(hex::encode_prefixed(&erc20::transfer(to, units)))
83}
84
85pub fn deposit_calldata(network: Network, amount: Dec) -> Result<String, BadDeposit> {
88 if amount.is_positive() && amount < MINIMUM_DEPOSIT {
89 return Err(BadDeposit::BelowMinimum(amount));
90 }
91 usdc_transfer_calldata(bridge(network), amount)
92}
93
94#[must_use]
97pub fn usdc_balance_request(network: Network, owner: Address) -> Query<Vec<u8>> {
98 eth::call(&CallRequest::to(usdc(network), erc20::balance_of(owner)), BlockTag::Latest)
99}
100
101#[must_use]
103pub fn eth_balance_request(owner: Address) -> Query<U256> {
104 eth::balance(owner, BlockTag::Latest)
105}
106
107pub fn usdc_amount(answer: &[u8]) -> Result<Dec, RpcError> {
109 let unreadable = |detail: String| RpcError::Unreadable { method: "eth_call", detail };
110 let units = erc20::read_amount(answer).map_err(|error| unreadable(error.to_string()))?;
111 units.to_dec(USDC_DECIMALS).map_err(|error| unreadable(error.to_string()))
112}
113
114pub fn eth_amount(wei: U256) -> Result<Dec, RpcError> {
116 wei.to_dec(NATIVE_DECIMALS)
117 .map_err(|error| RpcError::Unreadable { method: "eth_getBalance", detail: error.to_string() })
118}
119
120#[cfg(test)]
121#[path = "tests/bridge.rs"]
122mod tests;