I'm Talfao, a security researcher and CEO of CODESPECT, a security firm focused on smart contract audits and penetration testing. Over the past few months, my team and I have been diving deep into the Hyperliquid system, especially around one of our main areas of expertise - Liquid Staking Token (LST) protocols.
While LSTs on Hyperliquid open exciting opportunities for decentralised staking, they also introduce hidden technical and security pitfalls that can silently undermine a protocol if left unchecked.
In this post, I'll walk you through the specifics of building an LST protocol on HyperEVM/HyperCore, the common pitfalls we've observed, and the safeguards required to build secure and sustainable staking systems.
Key points we'll cover:
- Precompiles - how to work with them (and what not to expect)
- The delegation–undelegation problem
- The withdrawal period and its implications
- Decimal mismatches between HyperEVM and HyperCore
- Potential staleness of data
Precompiles and CoreWriter
The Hyperliquid ecosystem has gained significant traction, with many projects starting to build on the HyperEVM layer. HyperEVM acts as a layer 2 on top of HyperCore, and its seamless interaction between these layers enables products that would be harder to create on other networks.
A key upgrade was the introduction of Precompiles and CoreWriter, which enable contracts on HyperEVM to interact with HyperCore.
Precompiles are special, predefined addresses that serve as APIs from which HyperEVM contracts can read data. They are listed in the Hyperliquid documentation, and we can see examples in L1Read.sol:
address constant POSITION_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000800; address constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; address constant VAULT_EQUITY_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000802; function spotBalance(address user, uint64 token) external view returns (SpotBalance memory) { bool success; bytes memory result; (success, result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(user, token)); require(success, "SpotBalance precompile call failed"); return abi.decode(result, (SpotBalance)); }
Precompile addresses follow the pattern 0x...080X, where X increases monotonically. Each one exposes different data. For example, the spot balance precompile (0x...0801) returns the spot balance of a given token for a specific user.
Note: Hyperliquid does not specify under which scenarios these static calls might fail - an important caveat for protocol developers.
While precompiles allow reading data (like GET requests), how do we submit actions (like POST requests)? That's where CoreWriter comes in.
The CoreWriter contract is deployed at 0x3333333333333333333333333333333333333333. It emits events that HyperCore interprets as actions. Its implementation looks like this:
contract CoreWriter { event RawAction(address indexed user, bytes data); function sendRawAction(bytes calldata data) external { // Spends ~20k gas for (uint256 i = 0; i < 400; i++) {} emit RawAction(msg.sender, data); } }
The ~20k gas loop acts as a rate limiter. By emitting RawAction events, we can trigger specific actions on HyperCore as msg.sender. The full set of actions and encodings is documented in the Hyperliquid developer docs.
Crafting an LST on HyperEVM
With this infrastructure, we can design a decentralised LST protocol that allows users to stake their native HYPE tokens and receive a liquid version in return.

Simplified flow:
- A user transfers HYPE into a StakingManager.
- The StakingManager mints LST tokens according to the current ratio.
- The HYPE is transferred to the predefined bridge address
0x2222222222222222222222222222222222222222. - Once received, the StakingManager's spot HYPE balance on HyperCore increases.
- The StakingManager then uses CoreWriter to deposit the spot HYPE balance into staking and delegate that staking balance to a validator.
Example staking deposit action:
uint8 constant COREWRITER_VERSION = 1; // Current version is 1 bytes3 constant STAKING_DEPOSIT = bytes3(0x000004); // Action ID for staking deposit CoreWriter(HypurrLib.CORE_WRITER).sendRawAction( abi.encodePacked(COREWRITER_VERSION, STAKING_DEPOSIT, abi.encode(depositAmount)) );
Staking rewards are automatically staked and delegated. The overall delegation state of any user can be read from another precompile (0x...0805). By supplying the user's address as input data, this precompile returns their current delegation state.
struct DelegatorSummary { uint64 delegated; uint64 undelegated; uint64 totalPendingWithdrawal; uint64 nPendingWithdrawals; }
- delegated — balance actively earning rewards
- undelegated — staking balance available for delegation or withdrawal
- totalPendingWithdrawal — amount in the 7-day withdrawal queue
- nPendingWithdrawals — number of pending withdrawal requests
Withdrawals require an undelegated balance, meaning you cannot withdraw directly from a fully delegated position. You first need to partially or fully undelegate from the validator. This makes delegation/undelegation logic a core part of secure LST design.
Secure Implementation Considerations
A robust LST protocol on Hyperliquid must account for:
- 7-day withdrawal period
- Slashing risk (different from Ethereum PoS, but still relevant)
- 1-day undelegation lockup before withdrawing
- No error handling in CoreWriter (it only emits events)
- Decimal mismatches between HyperEVM and HyperCore
- Potential staleness of data returned by precompiles
Withdrawal Pitfalls
The 7-day withdrawal period makes instant redemptions impossible. Instead, protocols need a two-step withdrawal process:
1. Request phase
- User transfers LST tokens to the protocol, and a withdrawal request is initiated.
- Protocol undelegates balance from a validator.
- Initiate withdrawal from staking to spot. (7-day waiting period)
- Record the HYPE amount owed per LST token at this moment.
2. Claim phase (after ~7 days)
- Move SPOT HYPE back to HyperEVM.
- Fulfil the initiated withdrawal request.
Additional considerations:
- Since slashing exists, the LST/HYPE ratio can change. Withdrawals should always settle to the lower of (recorded amount vs. current calculated amount).
- Withdrawals should always take priority over new delegations to prevent conflicts during undelegation. Without this ordering, undelegations can fail because any new delegation to the same validator by the same user resets the one-day lockup period of the delegated balance, blocking undelegations.
Decimal Differences
A subtle but important pitfall when building on Hyperliquid is the decimal mismatch:
- HyperEVM: HYPE uses 18 decimals.
- HyperCore (spot): HYPE uses 8 decimals.
When transferring across the bridge, any precision beyond 8 decimals is truncated.
If your protocol mints LST tokens based on the full 18-decimal deposit, users could end up with more LST than the actual underlying HYPE, causing gradual devaluation.
Mitigations:
- Restrict deposits to multiples of
1e8. - Truncation buffer (recommended) - redirect truncated amounts to a buffer contract to cover withdrawals or act as a safety margin.
CoreWriter Pitfalls
The CoreWriter contract is only an event emitter. It provides no error handling and does not revert if the corresponding HyperCore action fails.
This means the HyperEVM transaction will succeed even if the HyperCore action silently fails.
Risks:
- State desynchronization between HyperEVM and HyperCore.
- LST accounting logic diverging from the true HyperCore state.
Mitigation strategies:
- Separation of concerns: Isolate all L1-related operations so silent failures do not corrupt protocol logic.
- Robust state accounting: Update internal variables in a way that remains consistent even if some actions fail (e.g., only consider actions applied once verified).
Staleness of Precompiled Data
Precompiles do not always return the latest state. According to Hyperliquid documentation:
The values are guaranteed to match the latest HyperCore state at the time the EVM block is constructed.
This means within a single block, precompiles return the state from the start of that block, even if new actions occur later.
We tested this behaviour using the following function:
function testFunction(address validator) external payable { uint64 truncatedValue = uint64(msg.value / 1e10); (bool success, bytes memory ret) = HypurrLib.L1BRIDGE.call{ value: msg.value }(""); CoreWriter(CORE_WRITER).sendRawAction( abi.encodePacked(VERSION, STAKING_DEPOSIT, abi.encode(truncatedValue)) ); CoreWriter(CORE_WRITER).sendRawAction( abi.encodePacked(Actions.VERSION, Actions.TOKEN_DELEGATE, abi.encode(validator, truncatedValue, false)) ); address DELEGATOR_PRECOMPILE = 0x0000000000000000000000000000000000000805; require(success, "Delegator summary precompile call failed"); require(ret.length > 0, "Empty return data from precompile"); DelegatorSummary memory del = abi.decode(ret, (DelegatorSummary)); delegated = del.delegated; undelegated = del.undelegated; emit DelegationUpdated(delegated, undelegated); }
If 2 HYPE were delegated before this transaction, and 1 HYPE is delegated within it, the delegated value will still return 2 instead of 3.
Mitigation:
- Maintain internal accounting variables for changes within a block.
- Do not rely on precompiles alone for fresh balances.
- Use precompiles for checkpointing between blocks, but rely on internal state for transactions within one block.
Conclusion
Building an LST on Hyperliquid introduces challenges that differ from traditional staking systems. The most important considerations are:
- We first need to undelegate the delegated (staked) balance prior to withdrawals. And we cannot undelegate if the delegation to the same validator happened within 24 hours.
- Decimal mismatches between HyperEVM (18) and HyperCore (8) can inflate supply if not addressed.
- CoreWriter actions may silently fail, requiring defensive accounting.
- Precompiled data may be stale within the same block, demanding internal state tracking.
Protocols must carefully account for these pitfalls to maintain safety, avoid inflation, and preserve user trust.
Any secure LST implementation must be designed for resilience against failure, accurate accounting, and safety-first withdrawal handling. By acknowledging these pitfalls upfront and implementing strict mitigations, developers can build protocols that are not only functional but also robust against subtle yet critical failure modes in the Hyperliquid environment.