EIP-4626 vault template for HyperEVM, with on-chain NAV via HyperCore precompiles and trade execution via the CoreWriter system contract. Replaces Hyperliquid's legacy native vaults (10,000 USDC creation fee, perps-only) with a gas-only Solidity contract that supports spot, perps, and HIP-3 markets in any quote.
Validated end-to-end on HyperEVM mainnet (chain 999) — see the mainnet findings below for what worked, what didn't, and the v1.2 fixes that shipped from that exercise.
- Audit-ready ERC-4626 vault (
src/HyperCoreVault.sol) — operator/emergency/admin roles, asset whitelist, leverage cap, slippage band, management + performance fees, withdrawal queue, cost-basis tracking - Per-strategy deploy pipeline — JSON config in, on-chain vault + per-vault
TimelockController+ auto-registered entry out - Discovery frontend — Vite + React + viem; auto-discovers every vault from
deployments/<chain>/*.jsonartifacts at build time - Python e2e runner — drives
deposit → push → spot→perp → place → cancel → perp→spot → pull → redeemagainst the live HL API - 51 forge tests — unit, integration (against mocked precompiles + CoreWriter), CoreWriter encoding bit-exactness, fee math, withdrawal queue, leverage/slippage gates
| Legacy HyperCore vault | HyperVault on HyperEVM | |
|---|---|---|
| Creation fee | 10,000 USDC | ~$1 in gas |
| Asset support | Perps only, USDC quote | Spot + perp + HIP-3, any quote |
| Composability | None — internal HL accounting | ERC-20 share token; DeFi-native |
| Custom logic | Fixed | Arbitrary Solidity (fees, lockups, whitelists, etc.) |
| Where it runs | HL Core matching engine | HyperEVM EVM contract; calls Core via CoreWriter (0x3333…3333) |
The vault contract lives on HyperEVM but its HyperCore account is automatically derived from its EVM address — so a single deploy gives you one ERC-4626 token, one EVM contract, and one Core account, all at the same address.
┌─────────────────────────────────────────────────┐
│ Operator (EOA / multisig) │
│ Emergency Admin (multisig) │
│ Timelock (24h) ─ admin role │
└────────────┬────────────────────────────────────┘
│ calls
┌────────────▼────────────────────────────────────┐
│ HyperCoreVault (one per strategy) │
│ - ERC4626 share token (12 dp) │
│ - AccessControl: OPERATOR / EMERGENCY / ADMIN │
│ - Pausable, ReentrancyGuard │
│ - Whitelist (perps + spots) │
│ - Leverage cap, slippage band, fees │
│ - cloid counter │
│ - Withdrawal queue escape hatch │
│ - operatorRecoverSpot, operatorSweepStranded │
└───────┬─────────────────────┬───────────────────┘
│ writes │ reads
▼ ▼
┌───────────────────┐ ┌────────────────────────┐
│ CoreWriter │ │ L1 read precompiles │
│ 0x333…3333 │ │ 0x0800 … 0x0810 │
└───────┬───────────┘ └────────────────────────┘
│ async dispatch
▼
┌──────────────────────────────────────────────┐
│ HyperCore matching engine │
│ Position / balance state per account │
└──────────────────────────────────────────────┘
Deep-dive: docs/ARCHITECTURE.md.
src/ Solidity sources
HyperCoreVault.sol Main ERC4626 vault per strategy
HyperCoreVaultFactory.sol CREATE2 factory (currently bypassed for size — see below)
HyperCoreVaultRegistry.sol On-chain directory of deployed vaults
libraries/
Constants.sol Precompile addresses, CoreWriter action IDs, TIF enum, USDC indices
CoreWriterLib.sol Typed wrappers for limit_order / spot_send / usd_class_transfer / cancel
PrecompileLib.sol Typed reads of all L1 precompiles (position, spotBalance, oraclePx, etc.)
AssetId.sol Perp/spot ID encoding (spot = 10_000 + spotIdx)
SystemAddress.sol Token bridge-address derivation (0x20 || zero-pad || tokenIdx)
interfaces/
ICoreWriter.sol
IHyperCoreVault.sol Full public ABI + events for indexers / frontend
script/ Foundry deploy scripts
DeployRegistry.s.sol One-time per chain
DeployMockUSDC.s.sol For testnet / fork where real USDC isn't available
Deploy.s.sol Per-strategy from JSON config; deploys timelock + vault + seeds whitelist
test/ 51 tests across 8 suites
unit/ Vault, fees, withdrawal queue, CoreWriter encoding bit-exactness, etc.
integration/ Full lifecycle via mocked precompiles + CoreWriter (CoreSimulator-style)
mocks/ MockCoreWriter, MockPrecompiles, MockUSDC
scripts/python/ Python orchestration (HL SDK + web3.py)
e2e_runner.py Drives full lifecycle on testnet/mainnet with HL API cross-checks
optin_big_blocks.py Toggles HyperEVM big-blocks via HL API (needed for vault deploy)
seed_vault_core.py Sends Core USDC from your account to a vault's Core address
seed_whitelist.py Post-deploy whitelist updates through the timelock
deployments/ Strategy configs (input) + deploy artifacts (output)
configs/ Per-strategy JSON parameter files
mainnet/ Per-strategy deploy artifacts written by Deploy.s.sol
testnet/
docs/ Architecture, integration, security, testnet runbook
ARCHITECTURE.md Design rationale + diagrams
INTEGRATION.md Live runner integration guide (event → SDK field mapping, runbook)
SECURITY.md Threat model, role/permission matrix, audit checklist, mainnet findings
TESTNET.md Step-by-step testnet walkthrough
frontend/ Vite + React + viem discovery UI
src/
App.tsx Orchestrator; groups by chain, async live state per vault
components/VaultCard.tsx NAV breakdown, fees, paused/shutdown banners
lib/
artifacts.ts Build-time glob of deployments/*/*.json
chains.ts Hyperliquid mainnet/testnet/local chain configs
abi.ts Minimal vault ABI for read-only discovery
fetcher.ts viem multicall reader for live NAV + state
HyperCoreVault.sol — the main contract. Per-strategy, EIP-4626-compliant. Notable surface:
deposit / mint / withdraw / redeem— standard ERC-4626 withmaxWithdrawcorrectly bounded by idle USDC (no silent reverts)placeLimitOrder / cancelOrderByCloid— operator-only, gated by asset whitelist + slippage band vsoraclePx+ post-trade leverage cappushToCore / pullFromCore— operator-only EVM↔Core USDC bridging (where the bridge is linked)operatorRecoverSpot(to, token, amountWei)— operator-only generic Core spot send; the fallback whenpullFromCore's bridge path isn't availableusdSpotToPerp / usdPerpToSpot— operator-only USD class transfersoperatorSweepStranded(to)— recovers EVMasset()balance whentotalSupply == 0(the donation-to-empty-vault recovery)emergencyCancelByCloid / emergencyCancelByOid / emergencyClosePositions / emergencyShutdown / pause / unpause— emergency-role onlysetWhitelistPerp / setWhitelistSpot / setLeverageCap / setSlippageBand / setFees / setDepositCap / sweep— admin (timelock) onlynav / pricePerShare / idleUsdc / coreSpotUsdc / perpWithdrawable— public view helpers, all backed by precompile reads
HyperCoreVaultRegistry.sol — on-chain directory. The frontend reads from deployments/*/*.json directly (no chain call needed for discovery), but the registry remains the canonical on-chain source. Owner OR factory can write.
HyperCoreVaultFactory.sol — currently bypassed in the deploy script. The factory's runtime bytecode (which inlines type(HyperCoreVault).creationCode) is 30KB+, over the EIP-170 24KB limit. Deploy.s.sol constructs the vault via plain CREATE from the script instead. A v1.1 refactor to a minimal-proxy (EIP-1167 Clones) pattern would restore the factory.
CoreWriterLib — wraps the CoreWriter system contract (0x3333…3333). Each typed function packs abi.encodePacked(uint8(1), uint24(actionId), abi.encode(args)) and calls sendRawAction. The action set: limit_order, cancel_order_by_oid, cancel_order_by_cloid, spot_send, usd_class_transfer, vault_transfer. Bit-exact encoding is verified by golden-vector tests in test/unit/CoreWriterLib.t.sol.
PrecompileLib — typed staticcall wrappers for every L1 read precompile (0x0800–0x0810). Returns the protocol's struct; falls back to zero-initialised struct if the precompile errors (e.g., the account has never touched that market). Used by the vault for totalAssets (NAV = idle + coreSpot + perpWithdrawable) and by the operator gates (oraclePx for slippage, position/markPx for leverage).
Constants, AssetId, SystemAddress — small pure helpers. AssetId encodes perp/spot IDs (spot = 10_000 + spotIdx). SystemAddress derives the per-token bridge address (0x20 || zero-pad || tokenIdx_BE).
forge script script/DeployRegistry.s.sol --rpc-url <RPC> --broadcast — one-time per chain.
STRATEGY_CONFIG=deployments/configs/<name>.json forge script script/Deploy.s.sol --rpc-url <RPC> --broadcast — per strategy. Reads JSON config → deploys per-vault TimelockController → deploys vault via CREATE → registers in registry → seeds whitelist via scheduleBatch + executeBatch (when timelockMinDelaySec == 0). Writes a typed artifact JSON to deployments/<chain>/<name>.json that the frontend auto-discovers.
scripts/python/e2e_runner.py — drives the full vault lifecycle on testnet/mainnet with HL API cross-checks at every step. Step-selectable via --steps, --skip-bridge mode for environments without a working EVM↔Core USDC bridge.
scripts/python/optin_big_blocks.py — toggles HyperEVM big-blocks via the HL API (required because the vault deploy is ~8M gas, over the 2M small-block limit).
scripts/python/seed_vault_core.py — sends Core USDC from your account to a vault's Core address (the workaround when the EVM↔Core bridge isn't linked for the chosen asset).
scripts/python/seed_whitelist.py — schedules + executes whitelist additions through the per-vault timelock.
Build-time auto-discovery via import.meta.glob('/deployments/*/*.json', { eager: true }). To add a new vault to the UI, just run the deploy script — the artifact is picked up on the next npm run dev / npm run build. Per-chain grouping, live NAV / fee / paused-state reads via viem multicall.
cd frontend
npm install
ln -sf ../deployments deployments # one-time, lets Vite glob the artifacts
npm run dev # http://localhost:5173- Foundry (
brew install foundryorcurl -L https://foundry.paradigm.xyz | bash && foundryup) - Node 20+ and npm
- Python 3.11+ (for the e2e runner)
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0 --shallow --no-git
forge install foundry-rs/forge-std --shallow --no-git
forge build
forge test # 51 testscp .env.example .env
# Fill in HYPEREVM_RPC_MAINNET, DEPLOYER_PRIVATE_KEY (with 0x prefix)
source .env
# One-time per chain:
forge script script/DeployRegistry.s.sol --rpc-url $HYPEREVM_RPC_MAINNET --broadcast
# Copy printed REGISTRY_MAINNET into .env; `source .env` again
# Per strategy:
cp deployments/configs/example.json deployments/configs/my-strategy.json
# Edit the config — operator/feeRecipient/usdcAddress/leverageCapBps/etc
STRATEGY_CONFIG=deployments/configs/my-strategy.json forge script script/Deploy.s.sol \
--rpc-url $HYPEREVM_RPC_MAINNET --broadcast --slowAfter deploy, opt the vault into big-blocks (only needed for emergency fan-out paths):
python3 -m pip install --user -r scripts/python/requirements.txt
OPERATOR_PRIVATE_KEY=$DEPLOYER_PRIVATE_KEY \
VAULT_ADDRESS=<printed-vault-address> \
NETWORK=mainnet \
python3 scripts/python/optin_big_blocks.pycd frontend
npm install
ln -sf ../deployments deployments
npm run devFull step-by-step (including faucets, big-blocks toggle, e2e runner) lives in docs/TESTNET.md.
The vault was validated end-to-end on HyperEVM mainnet. Three concrete bugs were found and fixed by running against real precompiles and CoreWriter (which our anvil-fork verification had hidden). One issue remains open and worth flagging before production use.
Fixed:
oraclePx/markPxprecompiles usehuman × 10^(6−szDecimals)scale, not the10^(8−szDecimals)scale that thelimit_orderCoreWriter action uses. A 100× mismatch in the slippage and leverage gates was breaking every realistic order. Fixed by normalizing oraclePx by 100 in the slippage band check and adjusting the leverage notional formula. Test mocks updated for the corrected scale.- EIP-170 contract-size limit on the factory. Inlining
type(HyperCoreVault).creationCodepushed the factory over 24KB. Worked around by deploying the vault directly fromDeploy.s.solvia CREATE; the factory remains in the repo for a future EIP-1167 refactor. operatorRecoverSpot(to, token, amountWei)added so the operator can move Core spot funds out of the vault when the EVM↔Core bridge for the chosen asset isn't deployed (the current mainnet state for USDC).operatorSweepStranded(to)added for recovering EVMasset()balance aftertotalSupplyreturns to zero.
Open finding:
- HL Core does not appear to process
limit_orderactions submitted via CoreWriter from a contract account. Other actions (spot_send,usd_class_transfer,send_asset) work correctly for vault contracts — funds move, ledger entries appear. ButplaceLimitOrderproduces zero entries in HL'shistoricalOrders, regardless of TIF (0/1/2/3 all tested) or order size ($0.76and$12+both attempted). The CoreWriter event fires on EVM, but HL Core is silent. Possible causes: requiressetLeverageinitialization (no CoreWriter wrapper exists for it), requiresadd_api_walletdelegation, or requiresuser_set_abstractionmode setup. Needs HL team input or further protocol research.
Documented in full in docs/SECURITY.md under "Lessons from mainnet testing (v1.2)".
| What | Address |
|---|---|
| HyperCoreVaultRegistry | 0xA430c24f63BB3245723242c7843b2E07BA220ba8 |
| Tier 1 vault (v1.0) | 0x1DDC8A2478157da455D7AafE7486CD674f7E5B1a |
| Tier 2 vault (v1.1, +recoverSpot) | 0xdc5196C7d841b2C3C6E935dE04383Fb40b8534aE |
| Tier 2b vault (v1.2, +slippage scale fix) | 0xC43997299722A3896ddBA28730a7ff2A6A6B02f5 |
| Mainnet USDC (Circle, bridged from Arbitrum) | 0xb88339CB7199b77E23DB6E890353E22632Ba630f |
Total mainnet spend across the full validation exercise: 0.011 HYPE ($0.50) in gas + 1 USDC HL transfer fee + 1.5 USDC stranded in the Tier 2b vault (donation-trap demonstration, recoverable via operatorSweepStranded if redeployed).
- EIP-1167 minimal-proxy refactor to restore the CREATE2 factory and keep the per-vault deploy under EIP-170
setLeverageCoreWriter wrapper oradd_api_walletflow to unblock perp order placement from contract accounts- Multi-quote vault support (current: USDC-only)
- Subaccount support (current: one Core account per vault, derived from EVM address)
- Sweep stranded asset is operator-gated; consider moving to
EMERGENCY_ROLEfor production - Operator NAV override for edge cases where precompiles return wrong values
docs/ARCHITECTURE.md— design rationale, NAV math, fee accountingdocs/INTEGRATION.md— live-runner integration, event-to-SDK-field mapping, runbookdocs/SECURITY.md— threat model, role matrix, audit checklist, mainnet findingsdocs/TESTNET.md— step-by-step testnet walkthroughscripts/python/README.md— Python helper usage
MIT