What one HyperEVM transaction can and cannot do
One HyperEVM transaction can read HyperCore and emit a perp order and an outcome operation, but it only enqueues them, so the legs are not atomic.
A CoreWriter call commits a log, not an action. The action runs later on HyperCore, which never reports back. Three research tracks hit this boundary: a perp-plus-outcome quoter, a vault supplying the HyperCore lending book, and a quorum-run deployer account.
Say your contract reads the mark price, emits an Ioc perp order and a split of 10 contracts (wei = 1000000000), then returns. The transaction succeeds before either leg has run. On mainnet the order lands 0.35 to 0.51 s after the block; if it fills and HyperCore refuses the split, nothing rolls back, and your contract holds a naked perp.
What fits in one transaction
Section titled “What fits in one transaction”| Step | Mechanism | What you get |
|---|---|---|
| Read | Precompiles 0x800 to 0x814 |
Positions, spot balances, mark and oracle prices, best bid and offer, lending reserve state, outcome status. The docs guarantee the values match the latest HyperCore state at the time the EVM block is constructed. |
| Compute | Ordinary EVM execution | Any function of what was read. |
| Emit a perp order | CoreWriter action 1 | A limit order with TIF Alo, Gtc or Ioc. There is no FrontendMarket from CoreWriter, so a market order is an Ioc with a slippage-bounded limitPx. |
| Emit an outcome operation | CoreWriter action 17 | Split, merge, merge question or negate. |
| Return | The EVM transaction | Success, whatever happens next. |
Details: reading HyperCore, writing to HyperCore, split, merge, negate.
Each leg succeeds or fails on its own
Section titled “Each leg succeeds or fails on its own”Enqueue is not execute. sendRawAction burns about 25,000 gas and emits a RawAction log. The documented order within an L1 block is: L1 block built, EVM block built, HyperEVM to HyperCore transfers, then CoreWriter actions.
Failures are silent. The HyperEVM is never told. Every refusal returns success, gas is spent, a log is emitted and the money stays put. Four refusals seen, each with EVM status 1 and visible only in HyperCore’s action log:
Invalid number of decimals(a HyperCore to HyperEVM amount that is not representable)Insufficient USDC or HYPE balance for token transfer gasBorrow/Lend error: Insufficient spot balance for operationManual borrow/lend not allowed with portfolio margin
Price can move between legs. The read is HyperCore as the EVM block was built; an Ioc priced off it fills fully, partly or not at all, and the outcome leg does not know which.
Conversions are deterministic, not guaranteed. A split mints equal Long and Short, a merge is its exact inverse, with no price or counterparty: 1 USDC split and merged returned exactly. But a payload with a non-zero unused field is dropped, and wei must be a whole multiple of one contract.
Only vault transfers are delayed
Section titled “Only vault transfers are delayed”The sample: 5,455 mainnet CoreWriter actions from 73,914 blocks (about 20.5 hours), each matched to its HyperCore effect, orders by the client order id in their own payload.
| Action | Network | n | Delay after the EVM block timestamp |
|---|---|---|---|
| 1, limit order (fills matched by client order id) | mainnet | 52 | 0.35 to 0.51 s, mean 0.412 s, sd 0.039 s |
| 2, vault transfer | mainnet | 6 | 4.05 to 4.10 s |
| 6, 7 and 13 (spot send, USD class transfer, send asset) | mainnet | 6 each | 0.006 to 0.46 s |
| 17, split and merge | testnet | 6 | in the transaction’s own block, 0.01 to 0.68 s |
| 1, resting limit order | testnet | 3 | in the transaction’s own block, 0.75 to 0.82 s |
The roughly 0.4 s floor is pipeline latency; on testnet both legs land in the transaction’s own block. That narrows timing risk but creates no atomicity.
The numbers
Section titled “The numbers”| Parameter | Value | Basis |
|---|---|---|
| CoreWriter address | 0x3333333333333333333333333333333333333333 |
docs |
| CoreWriter gas | about 25,000 burned, about 47,000 for a basic call; 47,889 measured on a refused action 15 | docs; spec 02 |
Action 1 limitPx and sz |
1e8 x the human value | docs; REFERENCE.md |
| Action 1 TIF | 1 Alo, 2 Gtc, 3 Ioc; no FrontendMarket |
docs |
Action 17 wei |
1e8 per contract; wei = 100000000 minted exactly one Long and one Short and moved 1 USDC |
spec 01 |
| Precompile perp price scale | divide by 10^(6 - szDecimals) |
docs |
| Precompile gas | 2000 + 65 * (input_len + output_len); a bad input consumes the whole frame, measured as PrecompileOutOfGas(50000000) |
docs; spec 02 |
| Order acceptance after the EVM block | 0.35 to 0.51 s (n=52, mainnet) | spec 01 |
| Vault transfer after the EVM block | 4.05 to 4.10 s (n=6, mainnet) | spec 01 |
| Mainnet action 17 traffic | 4 emissions in 3,000,001 blocks; none in the 73,914-block timing window | spec 02; spec 01 |
Put the risk in the deterministic leg
Section titled “Put the risk in the deterministic leg”Splitting collateral and quoting both Long and Short is hedged by construction and exits at exactly 1 through a merge. Pairing an outcome leg with a perp hedge leaves a naked perp whenever one leg is refused. The rate-options track chose split-and-quote for atomicity, not delay. Bounded payoffs also degrade gracefully: a scalar band pays between 0 and 1 whatever happens, a perp leg does not.
Treat every emission as a request:
- Persist the intent before emitting.
- Emit, with a client order id on every order.
- In a later block, read the effect from a precompile and settle only against what HyperCore did.
- Verify against a cumulative figure and keep one action of each kind in flight: two same-block intents checked against one snapshot both settled when only one was funded (composable credit).
// Illustrative. One transaction, two enqueued actions. Neither has executed when this returns.interface ICoreWriter { function sendRawAction(bytes calldata data) external; }ICoreWriter constant CW = ICoreWriter(0x3333333333333333333333333333333333333333);
function quote(uint32 perpAsset, uint64 limitPx1e8, uint64 sz1e8, uint128 cloid, uint32 outcome, uint64 contracts) external { // Leg 1: IOC perp order. tif 3 = Ioc. Price and size are 1e8 x the human value. CW.sendRawAction(abi.encodePacked(uint8(1), uint24(1), abi.encode(perpAsset, true, limitPx1e8, sz1e8, false, uint8(3), cloid))); // Leg 2: split. op 0 = SplitOutcome. question must be 0 or the payload is dropped. CW.sendRawAction(abi.encodePacked(uint8(1), uint24(17), abi.encode(uint8(0), uint32(0), outcome, contracts * 1e8))); // This transaction will succeed whether either leg later runs or not.}To read the result: 0x801 spot balances accept the outcome asset id 100000000 + 10 * outcome + side; 0x800 or 0x813 return the perp position. Off chain, the explorer’s userDetails returns each HyperCore action with an error field, the only record of a refusal.
Six constraints to design around
Section titled “Six constraints to design around”- The account must already exist as an activated HyperCore user before the EVM block (the first transfer to it costs 1 quote token). Deploy, fund, then act, across blocks.
- Multi-sig accounts cannot use CoreWriter (per the docs), so a quorum-run deployer cannot be run from a contract: see collateral underwriting.
- Spot orders were dropped silently on one testnet account, even above minimum notional, while the identical perp order succeeded. Small sample: verify every spot order.
- No history. Precompiles answer only at the latest block;
eth_callat any explicit block number, even the head, fails. Log what you read. - Two scales. A
0x806price is scaled by10^(6 - szDecimals), the same price in action 1 by 1e8. Crossing them is silently off by orders of magnitude. - Mainnet action 17 timing is inferred: measured on testnet only; the mainnet window had no action 17 traffic.
For the canonical account of what runs when, see Execution order and timing. Every silent failure, with its fix, is in Footguns, measured.