hyperliquid.evm_escrow
Documentation for eth_defi.hyperliquid.evm_escrow Python module.
EVM escrow lifecycle for HyperEVM-to-HyperCore bridging.
When USDC is bridged from HyperEVM to HyperCore via CoreDepositWallet.deposit(),
the funds do not appear in the user’s HyperCore spot account immediately.
Instead they pass through an intermediate EVM escrow stage:
EVM transaction lands –
CoreDepositWallet.deposit(amount, SPOT_DEX)burns USDC on HyperEVM and queues a bridge action.EVM escrow – the deposited amount appears in the
evmEscrowsfield of the user’sspotClearinghouseState. The funds are locked here while HyperCore’s L1 processes the bridge.HyperCore processing – within a few seconds (typically 2-4 s on mainnet, up to ~10 s on testnet under load) HyperCore picks up the queued action. The escrow entry disappears and the amount is credited to the user’s HyperCore spot balance.
Contract activation prerequisite
Smart contract addresses (such as Safe multisigs) must be activated
on HyperCore before CoreDepositWallet.deposit() bridge actions will
clear the EVM escrow. Without activation, deposited USDC gets permanently
stuck in the evmEscrows field and never reaches the spot account.
Activation is performed via CoreDepositWallet.depositFor(safe, amount, dex)
which bridges USDC to the Safe’s HyperCore spot and creates the account.
New HyperCore accounts incur a 1 USDC account creation fee, so the
minimum activation amount must be >1 USDC (deposits ≤1 USDC to new
accounts fail silently). The default activation amount is 2 USDC.
Note
CoreWriter.sendRawAction(spotSend) does not work for activation:
HyperCore silently drops spotSend actions targeting non-existent
addresses (the EVM transaction succeeds but no USDC is transferred).
Use is_account_activated() to check activation status and
activate_account() to perform the activation before depositing.
Why this matters for multi-step deposit flows
The old 4-step single-multicall deposit batched all actions into one EVM block:
approveUSDC to CoreDepositWalletCoreDepositWallet.deposit()– bridge USDC to HyperCore spotCoreWriter.sendRawAction(transferUsdClass)– move from spot to perpCoreWriter.sendRawAction(vaultTransfer)– deposit into vault
Steps 3-4 depend on the USDC having cleared the EVM escrow and being available in the spot account. Because all four steps land in the same EVM block, HyperCore processes them in a single batch. In practice this works most of the time, but under heavy load the bridge step can take longer, causing steps 3-4 to silently fail on HyperCore while the EVM transaction succeeds.
The fix is to split the deposit into two phases with an escrow wait:
Phase 1:
approve+CoreDepositWallet.deposit()Wait: poll
spotClearinghouseStateuntil theevmEscrowsentry for USDC disappears (meaning funds arrived in spot).Phase 2:
transferUsdClass+vaultTransfer
Checking escrow status
Use fetch_spot_clearinghouse_state() from eth_defi.hyperliquid.api:
from eth_defi.hyperliquid.api import fetch_spot_clearinghouse_state
from eth_defi.hyperliquid.session import create_hyperliquid_session
session = create_hyperliquid_session()
state = fetch_spot_clearinghouse_state(session, user="0xAbc...")
if state.evm_escrows:
for e in state.evm_escrows:
print(f"{e.coin}: {e.total} in escrow")
else:
print("No EVM escrows -- funds have cleared")
Expected latencies
Mainnet: 2-4 seconds typical, up to 10 seconds under load.
Testnet: 2-10 seconds typical, can be slower during congestion.
Maximum observed: ~30 seconds in extreme cases.
The wait_for_evm_escrow_clear() helper uses conservative defaults
(60 s timeout, 2 s poll interval) to handle worst-case scenarios.
Known issues
depositForon HyperEVM testnet does not create HyperCore accounts for contract addresses (e.g. Safe multisigs). The EVM transaction succeeds but the account is never created and USDC is lost. See hyperliquid-dex/node#138.
Module Attributes
coreUserExists precompile address on HyperEVM. |
|
Default USDC amount (raw, 6 decimals) for account activation. |
Functions
|
Activate a Safe's HyperCore account via |
|
Activate a HyperCore account via deployer-sponsored |
|
Check if an address is activated on HyperCore. |
|
Wait until the user's EVM escrow is empty (all bridged funds have cleared). |
- CORE_USER_EXISTS_ADDRESS = '0x0000000000000000000000000000000000000810'
coreUserExists precompile address on HyperEVM. Returns whether an address exists on HyperCore. See PrecompileLib.sol in hyper-evm-lib.
- DEFAULT_ACTIVATION_AMOUNT = 2000000
Default USDC amount (raw, 6 decimals) for account activation. New HyperCore accounts incur a ~1 USDC fee, so the minimum activation deposit must comfortably exceed that.
- is_account_activated(web3, user)
Check if an address is activated on HyperCore.
Uses the
coreUserExistsprecompile atCORE_USER_EXISTS_ADDRESSto definitively check whether the address exists on HyperCore.Smart contracts (like Safe multisigs) must be activated before
CoreDepositWallet.deposit()bridge actions will clear the EVM escrow. Seeactivate_account().Note
P12: Precompile reads return stale data within the same block as a CoreWriter action. This should not affect activation checks in practice because activation (
depositFor) and deposit (CDW.deposit) are in separate transactions, and the trade executor waits for activation receipt before proceeding.Example:
from eth_defi.hyperliquid.evm_escrow import is_account_activated from eth_defi.provider.multi_provider import create_multi_provider_web3 web3 = create_multi_provider_web3("https://rpc.hyperliquid.xyz/evm") if is_account_activated(web3, user="0xAbc..."): print("Account is activated") else: print("Account needs activation")
- activate_account(web3, lagoon_vault, deployer, session=None, activation_amount=2000000, timeout=60.0, poll_interval=2.0)
Activate a Safe’s HyperCore account via
depositFor.Smart contracts (like Safe multisigs) must be activated on HyperCore before
CoreDepositWallet.deposit()bridge actions will work. Without activation, deposited USDC gets permanently stuck in theevmEscrowsfield.The activation flow uses
transact_via_trading_strategy_moduleto callCoreDepositWallet.depositFor(safe, amount, SPOT_DEX)through the Safe’s trading strategy module. This bridges USDC from the Safe’s EVM balance to the Safe’s HyperCore spot account, creating the account in the process.Note
New HyperCore accounts incur a 1 USDC account creation fee. Deposits ≤1 USDC to new accounts fail silently. The default
activation_amountof 2 USDC comfortably exceeds the fee.Warning
The Safe must hold sufficient EVM USDC for the activation amount. The guard must have
depositForwhitelisted viawhitelistCoreWriter()(included since guard v0.x).Example:
from eth_defi.hyperliquid.evm_escrow import activate_account activate_account( web3=web3, lagoon_vault=lagoon_vault, deployer=deployer_wallet, )- Parameters
web3 (Web3) – Web3 connection to HyperEVM.
lagoon_vault (LagoonVault) – Lagoon vault instance with
trading_strategy_module_addressconfigured. The Safe associated with this vault will be activated.deployer (HotWallet) – Hot wallet for the asset manager / deployer EOA.
session (HyperliquidSession | None) – Optional Hyperliquid API session. If provided, the function checks that the Safe has no existing EVM escrow entries before attempting activation. Stuck escrow entries from prior failed deposits will prevent activation from succeeding.
activation_amount (int) – USDC amount in raw units (6 decimals) to deposit for activation. Defaults to 2 USDC (
DEFAULT_ACTIVATION_AMOUNT). Must comfortably exceed the ~1 USDC account creation fee.timeout (float) – Maximum seconds to wait for activation verification. Defaults to 60 seconds.
poll_interval (float) – Seconds between precompile polls. Defaults to 2 seconds.
- Raises
TimeoutError – If the activation does not complete within the timeout period.
- Return type
None
- activate_account_sponsored(web3, target_address, deployer, usdc_address, session=None, activation_amount=2000000, timeout=60.0, poll_interval=2.0)
Activate a HyperCore account via deployer-sponsored
depositFor.Unlike
activate_account()which routes through the Safe’s trading strategy module, this function has the deployer EOA callCoreDepositWallet.depositFor(target, amount, SPOT_DEX)directly. The USDC comes from the deployer’s own EVM balance, not the Safe’s.This is a workaround for the testnet issue where
depositForcalled through a Smart contract (Safe multisig) does not create HyperCore accounts. See hyperliquid-dex/node#138.Note
New HyperCore accounts incur a 1 USDC account creation fee. Deposits ≤1 USDC to new accounts fail silently. The default
activation_amountof 2 USDC comfortably exceeds the fee.Example:
from eth_defi.hyperliquid.evm_escrow import activate_account_sponsored activate_account_sponsored( web3=web3, target_address="0xAbc...", deployer=deployer_wallet, usdc_address="0xDef...", )- Parameters
web3 (web3.main.Web3) – Web3 connection to HyperEVM.
target_address (Union[eth_typing.evm.HexAddress, str]) – The address to activate on HyperCore (e.g. a Safe multisig).
deployer (eth_defi.hotwallet.HotWallet) – Hot wallet for the deployer EOA that will pay for the activation. Must hold sufficient EVM USDC.
usdc_address (Union[eth_typing.evm.HexAddress, str]) – USDC token contract address on HyperEVM.
session (eth_defi.hyperliquid.session.HyperliquidSession | None) – Optional Hyperliquid API session. If provided, checks that the target has no existing EVM escrow entries before attempting activation.
activation_amount (int) – USDC amount in raw units (6 decimals) to deposit for activation. Defaults to 2 USDC (
DEFAULT_ACTIVATION_AMOUNT).timeout (float) – Maximum seconds to wait for activation verification. Defaults to 60 seconds.
poll_interval (float) – Seconds between precompile polls. Defaults to 2 seconds.
- Raises
TimeoutError – If the activation does not complete within the timeout period.
- Return type
None
- wait_for_evm_escrow_clear(session, user, timeout=60.0, poll_interval=2.0, expected_usdc=None)
Wait until the user’s EVM escrow is empty (all bridged funds have cleared).
Waits one
poll_intervalbefore the first check to give HyperCore time to register the escrow entry (the API can lag behind the EVM tx). Then pollsspotClearinghouseStateuntilevmEscrowsis empty, indicating that allCoreDepositWallet.deposit()actions have been processed and funds are available in the user’s spot account.P15: When
expected_usdcis provided, also verifies that the user’s HyperCore spot USDC balance increased by at least the expected amount. This provides a dual check: escrow empty and USDC arrived in spot. Without this, a race condition where the escrow entry disappears but USDC is lost (e.g. burned by a failed bridge action) would go undetected.Example:
from eth_defi.hyperliquid.evm_escrow import wait_for_evm_escrow_clear from eth_defi.hyperliquid.session import create_hyperliquid_session session = create_hyperliquid_session() wait_for_evm_escrow_clear(session, user="0xAbc...", expected_usdc=Decimal("100")) # Now safe to proceed with CoreWriter actions that need spot balance- Parameters
session (HyperliquidSession) – Session from
create_hyperliquid_session().user (str) – On-chain address whose escrow to monitor (the Safe address for Lagoon vaults).
timeout (float) – Maximum seconds to wait before raising
TimeoutError. Defaults to 60 seconds which is conservative for typical 2-10 s latency.poll_interval (float) – Seconds between API polls. Defaults to 2 seconds.
expected_usdc (Decimal | None) – Optional expected USDC increase in the spot balance (human units, e.g.
Decimal("50")for 50 USDC). When provided, the function captures the pre-existing USDC spot balance and verifies that the balance increased by at least this amount after escrows clear.
- Raises
TimeoutError – If the escrow does not clear within the timeout period.
- Return type
None