A unified framework that combines Solidity libraries for HyperEVM development with a two-tier testing system for HyperCore interactions. Install via forge install — a pure Foundry library, no external dependencies.
| Layer | Language | Purpose |
|---|---|---|
src/ |
Solidity | CoreWriterLib, PrecompileLib, TokenRegistry — production libraries |
test/simulation/ |
Solidity | Level 1 & 2 — local HyperCore simulation for Foundry tests |
forge install endix-foundation-v1/hyper-evm-toolkit
echo "@hyper-evm-toolkit/=lib/hyper-evm-toolkit/" >> remappings.txtFunctions to call CoreWriter actions, with helpers to:
- Bridge tokens to/from Core
- Convert spot token amount representation between EVM and Core (wei) decimals
Query native read precompiles, including functions that accept EVM token addresses directly (no manual token index management).
On-chain mapping from EVM contract addresses to HyperCore token indices, populated trustlessly via precompile lookups. See TokenRegistry.sol.
A two-tier simulation engine for HyperCore interactions. Tests run in seconds locally — no testnet deployments needed.
VHC is a local simulation of HyperCore that runs entirely inside Foundry. It replaces the real HyperCore precompiles, CoreWriter, and CoreDepositWallet with simulated contracts, enabling fast, deterministic tests for any protocol that interacts with Hyperliquid's L1.
How it works: CoreSimulatorLib.init() etches simulated bytecode at the canonical system addresses (0x3333... for CoreWriter, 0x9999... for HyperCore, 0x0800–0x0810 for precompiles). Your contracts call CoreWriterLib and PrecompileLib as normal — they hit the simulated contracts instead of real precompiles.
All order settlement flows through dedicated bridge functions:
- Spot:
applyBridgeActionResult()— updates spot balances, deducts fees, emitsBridgeActionApplied - Perps:
applyPerpBridgeActionResult()— updates perp positions (open/increase/close with PnL), deducts fees, emitsPerpBridgeActionApplied
Both paths mirror how the real Hyperliquid bridge settles orders from EVM.
Your Contract
│
├─ CoreWriterLib.placeLimitOrder() ──► CoreWriterSim (ring buffer queue)
├─ PrecompileLib.spotBalance() ──► PrecompileSim (reads from HyperCore state)
├─ PrecompileLib.perpBalance() ──► PrecompileSim (reads perp positions)
│
└─ nextBlock() ──► CoreSimulatorLib
├─ Advance block number + timestamp
├─ Check deferred spot + perp limit orders
├─ Auto-apply queued spot orders via applyBridgeActionResult()
└─ Auto-apply queued perp orders via applyPerpBridgeActionResult()
| Mode | Activation | Behavior |
|---|---|---|
| Offline (default) | forge test |
Pure local simulation. Zero RPC calls. All state is mocked. Fast. |
| Fork | FORK_MODE=true forge test |
Forks real Hyperliquid chain state. Falls back to real chain data for unmocked state. |
| Tier | Settlement Method | Learning Curve | Best For |
|---|---|---|---|
| Level 1 — Auto-Apply | nextBlock() auto-fills all queued spot and perp orders at the limit price |
Zero | Most protocol tests |
| Level 2 — Explicit Settlement | consumeAllAndReturn() + manual applyFilledSpotOrder(), applyFilledPerpOrder(), etc. |
Moderate | Testing rejection, partial fill, error handling |
All VHC tests inherit from BaseSimulatorTest:
import {BaseSimulatorTest} from "@hyper-evm-toolkit/test/BaseSimulatorTest.sol";
import {CoreSimulatorLib} from "@hyper-evm-toolkit/test/simulation/CoreSimulatorLib.sol";
contract MyProtocolTest is BaseSimulatorTest {
address alice = makeAddr("alice");
function setUp() public override {
super.setUp(); // Initializes HyperCore, CoreWriter, precompiles
CoreSimulatorLib.forceAccountActivation(alice);
CoreSimulatorLib.forceSpotBalance(alice, 0, 1_000_000e8); // 1M USDC
}
}Place orders, call nextBlock(), balances update automatically.
function test_spotBuy() public {
vm.prank(alice);
CoreWriterLib.placeLimitOrder(
uint32(10000 + SPOT_INDEX), true, uint64(100e8), uint64(2e8),
false, HLConstants.LIMIT_ORDER_TIF_GTC, uint128(42)
);
// Auto-fills all queued orders via applyBridgeActionResult()
CoreSimulatorLib.nextBlock();
// Balances updated: base credited, quote debited (including 0.04% fee)
assertGt(PrecompileLib.spotBalance(alice, BASE_TOKEN).total, baseBefore);
}Orders with limit prices that don't match the current spot price are deferred and re-checked on subsequent nextBlock() calls.
Perp orders work the same way — place the order, call nextBlock(), positions update automatically.
function test_perpLong() public {
// Initialize perp account with USDC margin
CoreSimulatorLib.forceAccountActivation(alice);
CoreSimulatorLib.forceSpotBalance(alice, 0, 1_000_000e8); // USDC for margin
// Set mark price for the perp asset
CoreSimulatorLib.setMarkPx(PERP_ASSET, 20e8); // $20 mark price
vm.prank(alice);
CoreWriterLib.placeLimitOrder(
uint32(PERP_ASSET), true, uint64(20e8), uint64(100e8),
false, HLConstants.LIMIT_ORDER_TIF_GTC, uint128(42)
);
// Auto-fills perp order via applyPerpBridgeActionResult()
CoreSimulatorLib.nextBlock();
// Position opened: sz > 0, avgEntryPrice set, fee deducted
RealL1Read.PerpBalance memory perp = PrecompileLib.perpBalance(alice, PERP_ASSET);
assertGt(perp.position.sz, 0);
}Perp mechanics:
- Asset IDs:
< 10000or>= 100000(e.g., HYPE perp = 150) - Size is scaled from 8-decimal CoreWriter format to the asset's
szDecimals(HYPE=2, BTC=5, ETH=4) - Fees:
notional × perpMakerFee / FEE_DENOMINATOR(default 0.015%) - PnL: Realized on close/reduce — computed from
avgEntryPricevs execution price - Deferred orders: Re-checked on
nextBlock()when mark price matches limit
Consume queued actions and apply outcomes manually for full control.
import {CoreWriterSim} from "@hyper-evm-toolkit/test/simulation/CoreWriterSim.sol";
function test_rejectedOrder() public {
vm.prank(alice);
CoreWriterLib.placeLimitOrder(/* ... */);
// Drain the queue — does NOT execute orders
CoreWriterSim.QueuedAction[] memory actions = CoreSimulatorLib.consumeAllAndReturn();
// Apply with explicit outcome
CoreSimulatorLib.applyRejectedAction(
actions[0], uint8(HyperCore.BridgeReasonCode.INSUFFICIENT_BALANCE)
);
}
function test_partialFill() public {
vm.prank(alice);
CoreWriterLib.placeLimitOrder(/* size=5e8 */);
CoreWriterSim.QueuedAction[] memory actions = CoreSimulatorLib.consumeAllAndReturn();
CoreSimulatorLib.applyPartialFilledSpotOrder(actions[0], uint64(2e8), uint64(95e8));
}Available Level 2 helpers — Spot:
| Function | Outcome | Balance Effect |
|---|---|---|
applyFilledSpotOrder(action, executionPrice) |
FILLED | Full balance update with fees |
applyPartialFilledSpotOrder(action, filledAmount, executionPrice) |
PARTIAL_FILLED | Partial balance update |
applyRejectedAction(action, reason) |
REJECTED | No balance change |
applyErrorAction(action, reason) |
ERROR | No balance change |
Available Level 2 helpers — Perps:
| Function | Outcome | Position Effect |
|---|---|---|
applyFilledPerpOrder(action, executionPrice) |
FILLED | Full position update with fees + PnL |
applyPartialFilledPerpOrder(action, filledSz, executionPrice) |
PARTIAL_FILLED | Partial position update |
applyRejectedPerpAction(action, reason) |
REJECTED | No position change |
applyErrorPerpAction(action, reason) |
ERROR | No position change |
function test_level2_rejectedPerpOrder() public {
vm.prank(alice);
CoreWriterLib.placeLimitOrder(uint32(PERP_ASSET), /* ... */);
CoreWriterSim.QueuedAction[] memory actions = CoreSimulatorLib.consumeAllAndReturn();
CoreSimulatorLib.applyRejectedPerpAction(
actions[0], uint8(HyperCore.BridgeReasonCode.INSUFFICIENT_BALANCE)
);
}Level 1 and Level 2 can be mixed freely in the same test, for both spot and perp orders.
Fees are computed inside the bridge settlement functions:
Spot fees (inside applyBridgeActionResult()):
quoteAmount = (filledAmount × executionPrice) / 1e8
feeAmount = (quoteAmount × spotMakerFee) / FEE_DENOMINATOR
| Parameter | Default | Description |
|---|---|---|
spotMakerFee |
400 |
0.04% (4 bps) |
FEE_DENOMINATOR |
1e6 |
Fee denominator |
CoreSimulatorLib.setSpotMakerFee(1000); // 0.1%
CoreSimulatorLib.setSpotMakerFee(0); // Disable feesPerp fees (inside applyPerpBridgeActionResult()):
notional = filledSz × executionPrice / 1e8
feeAmount = notional × perpMakerFee / FEE_DENOMINATOR
| Parameter | Default | Description |
|---|---|---|
perpMakerFee |
150 |
0.015% (1.5 bps) |
FEE_DENOMINATOR |
1e6 |
Fee denominator |
CoreSimulatorLib.setPerpMakerFee(300); // 0.03%
CoreSimulatorLib.setPerpMakerFee(0); // Disable perp fees| Function | Description |
|---|---|
nextBlock() |
Advance one block. Auto-fills executable spot and perp orders. |
nextBlock(bool expectRevert) |
Same, but doesn't revert on action failures when true. |
| Function | Description |
|---|---|
getQueuedActionCount() |
Number of pending actions. |
getDeferredOrderCount() |
Number of deferred spot limit orders. |
consumeAllAndReturn() |
Drain queue and return all actions. |
applyFilledSpotOrder(action, executionPrice) |
Apply FILLED outcome. |
applyPartialFilledSpotOrder(action, filledAmount, executionPrice) |
Apply PARTIAL_FILLED outcome. |
applyRejectedAction(action, reason) |
Apply REJECTED outcome. |
applyErrorAction(action, reason) |
Apply ERROR outcome. |
| Function | Description |
|---|---|
getDeferredPerpOrderCount() |
Number of deferred perp limit orders. |
applyFilledPerpOrder(action, executionPrice) |
Apply FILLED perp outcome. |
applyPartialFilledPerpOrder(action, filledSz, executionPrice) |
Apply PARTIAL_FILLED perp outcome. |
applyRejectedPerpAction(action, reason) |
Apply REJECTED perp outcome. |
applyErrorPerpAction(action, reason) |
Apply ERROR perp outcome. |
applyPerpBridgeActionResult(...) |
Raw passthrough to HyperCore perp bridge. |
| Function | Description |
|---|---|
forceAccountActivation(addr) |
Activate a Core account. |
forceSpotBalance(addr, token, wei) |
Set spot balance. |
forcePerpBalance(addr, perpAsset, margin) |
Set perp margin balance. |
forcePerpPositionLeverage(addr, perpAsset, leverage) |
Set perp position leverage. |
setSpotPx(spotMarketId, price) |
Set spot market price. |
setMarkPx(perpAsset, price) |
Set perp mark price. |
setSimulatedL1BlockNumber(l1Block) |
Control L1 block number. |
setRevertOnFailure(bool) |
Toggle revert on failure. |
setSpotMakerFee(bps) |
Set spot fee rate. |
setPerpMakerFee(bps) |
Set perp fee rate. |
See the examples directory for production library usage.
For testing framework examples:
VHCForkExtensionsTest.t.sol— Level 1 auto-apply (spot)VHCLevel2Test.t.sol— Level 2 explicit settlement (spot)VHCFeeSettlementTest.t.sol— Fee settlement and deferred ordersVHCPerpLevel1Test.t.sol— Level 1 auto-apply (perps): open/close/increase positions, PnL, fees, deferred ordersVHCPerpLevel2Test.t.sol— Level 2 explicit settlement (perps): filled, partial, rejected, error outcomesCoreSimulatorTest.t.sol— General simulation tests
bridgeToEvm()for non-HYPE tokens requires the contract to hold HYPE on HyperCore for gas; otherwise, thespotSendwill fail.- Be aware of potential precision loss in
evmToWei()when the EVM token decimals exceed Core decimals, due to integer division during downscaling. - Ensure that contracts are deployed with complete functionality to prevent stuck assets in Core (e.g., implementing
bridgeToCorebut notbridgeToEvmcan lead to stuck assets on HyperCore). - Precompiles return data from the start of the block — CoreWriter actions won't be reflected until the next call.
This toolkit is a fork and extension of hyper-evm-lib by Obsidian Audits (0xjuaan, 0xSpearmint), with additions by Endix:
- Virtual HyperCore (VHC) — Two-tier local testing framework for spot and perps
- Perp Bridge Settlement — Full perpetual contract simulation with position tracking, PnL, fees, and deferred orders
For support, bug reports, or integration questions, open an issue.
Contributions welcome. Help us make building on Hyperliquid as smooth and secure as possible.