KeeperHub / KeeperHub/keeperhub
feat(protocols): add HyperEVM (chain 999) to the wrapped, Pyth and LayerZero protocols
- Dominant language
- TypeScript
- Stars
- 24
- Forks
- 93
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 266
Description
### Before filing
- [x] I searched open and closed issues for this proposal.
- [x] I checked the docs and the current behaviour on `staging`.
- [x] This is one change, not several. (Several means several issues.)
### Reason: what you cannot do today
No protocol in the repo lists chain 999. Once HyperEVM is a supported chain (#2504), a workflow can select it on a generic Web3 action, but every protocol action resolves its address from a per-chain map and none of those maps has an entry for it, so the whole protocol surface is empty on a chain the platform otherwise supports.
That leaves the same three things unavailable on HyperEVM that work everywhere else:
- wrapping and unwrapping the native token through the Wrapped Native actions;
- reading a Pyth price feed on the chain the workflow is already acting on, rather than reading a price from another chain and acting on this one;
- checking a LayerZero message's execution state on HyperEVM, which matters more here than on most chains because HyperEVM's dollar stablecoins arrive over LayerZero.
The product states the gap itself. `protocols/hyperliquid.ts` is `hubOnly: true` with no contracts and no actions, described as "On-chain HyperEVM read/write actions are coming soon", with a comment saying to add real contracts and actions "once HyperEVM on-chain support lands".
Checked on `staging` at `842ec84`: no `"999"` key in any map under `protocols/`, and no open issue or PR adding one.
### Reason: what the workaround costs
There is no in-product workaround for the wrapped and Pyth cases: a protocol action offers only the chains in its own address map, so HyperEVM does not appear and no configuration reaches it.
The Web3 `read-contract` action can reach any of these contracts on any chain, given a pasted ABI and address. That is the workaround, and its cost is the thing the protocol definitions exist to remove: the user supplies and maintains the ABI, the address is unverified and untyped, a mistyped address silently reads the wrong contract, and none of the action's inputs or outputs are labelled.
### Scope: what this touches, and what it does not
Every protocol declares addresses as `addresses: { "": "0x..." }`, so each entry below is one line in an existing map. No behaviour changes on any existing chain, and no new action is added.
Touches:
- **`protocols/wrapped.ts`:** WHYPE under `"999"`.
- **`protocols/pyth.ts`:** the Pyth contract under `"999"` in `PYTH_ADDRESSES`.
- **`protocols/layerzero.ts`:** EndpointV2 and EndpointV2View under `"999"`.
- **Protocol unit tests:** these suites pin exact chain sets rather than only address shape (`protocol-pyth.test.ts` asserts "across all 7 supported chains", `protocol-wrapped.test.ts` pins the chain list), so each set grows by one.
- **`docs/plugins/`:** the supported-chain lines for the three protocols.
Does not touch:
- **Dropping `hubOnly` from `protocols/hyperliquid.ts`.** That needs HyperEVM-native protocol addresses (HyperLend, Felix, HyperSwap) which I have not verified, and removing `hubOnly` changes what the hub page promises. A separate issue once these land.
- **New actions.** Existing actions gain a chain; none is added.
- **Anything on the LayerZero endpoint's own reference OFT map.** #2506 keeps that at its original chains deliberately, and this issue does not widen it.
### Plan: what you propose
Every value below was verified live against `https://rpc.hyperliquid.xyz/evm` on 2026-09-16, which answers `eth_chainId` `0x3e7` (999).
**WHYPE, for `protocols/wrapped.ts`**
`0x5555555555555555555555555555555555555555`
- `symbol()` returns WHYPE, `decimals()` returns 18.
- `totalSupply()` is about 5,043,991 WHYPE, so it is the chain's live wrapped native token rather than a stub.
- The deployed bytecode carries the WETH9 selectors the protocol's actions call: `deposit()` (`0xd0e30db0`) and `withdraw(uint256)` (`0x2e1a7d4d`), alongside the ERC-20 set.
**Pyth, for `protocols/pyth.ts`**
`0xe9d69CdD6Fe41e7B621B4A688C5D1a68cB5c8ADc`
- `getValidTimePeriod()` returns 60, matching the other chains' deployments.
- `getPriceUnsafe()` on the ETH/USD feed returned a live price at exponent -8, published the same day, so feeds are actually being updated on this chain rather than merely deployed.
**LayerZero, for `protocols/layerzero.ts`**
| Contract | Address | Verified |
|---|---|---|
| EndpointV2 | `0x3a73033c0b1407574c76bdbac67f126f6b4a9aa9` | `eid()` returns 30367 on chain, matching LayerZero's metadata entry for `hyperliquid-mainnet` |
| EndpointV2View | `0xc39161c743d0307eb9bcc9fef03eeb9dc4802de7` | Holds the same 2304 bytes of code as every other chain's view and answers `executable()` with a valid state |
The endpoint ID was taken from the deployment's own `eid()` call rather than matched on `nativeChainId`, for the reason #2506 documents: that field is not unique in LayerZero's metadata.
**Sequencing.** These do not become useful at the same time, so I would land them in two PRs rather than one:
1. **wrapped and Pyth** — needs #2504 only.
2. **LayerZero** — needs #2504 and #2506, since #2506 rewrites those same address maps. #2506's description already names this entry as the follow-up it deliberately left out.
**Verification plan.**
- Unit tests, extending the chain-set assertions each protocol suite already pins.
- The existing calldata goldens, regenerated, to show the encoding is unchanged.
- For LayerZero, the on-chain integration suite covers the new entry automatically: it iterates the view address map, so chain 999 joins the 22 chains it already calls.
- A read against each contract on a local instance once the chain seed exists.
**Questions for triage:**
1. Does WHYPE belong in `wrapped.ts` given the native token is HYPE rather than ETH? The contract is a standard WETH9 and the existing entries are all wrapped natives, so the pattern fits, but the action labels say ETH in places and may want a look.
2. Would you rather gate Pyth on a specific feed set, or list the chain and let the feed ID stay a user input as it is today?
3. Two PRs as above, or one once both dependencies have landed?
### Plan: alternatives you considered
- **Folding these into #2504.** Reasonable, and I would rather do that than open a second PR if you prefer it. I kept them apart because #2504 is a chain-onboarding change already carrying a gas-strategy fix, and mixing protocol address maps into it widens its review surface for no gain.
- **Adding HyperEVM DeFi protocols (HyperLend, Felix, HyperSwap) at the same time.** Each needs its own live verification and its own protocol definition. Rejected as too much for one issue, and I have not verified those addresses.
- **Leaving protocol coverage to `read-contract`.** That is the status quo, and it is what the protocol definitions exist to replace: an unverified pasted address and ABI on every workflow that touches the chain.
### Scope: compatibility
- [ ] Changes an existing response shape, status code, CLI flag, or default.
- [ ] Adds, removes, or upgrades a dependency.
- [ ] Changes database schema or requires a migration.
- [ ] Touches authentication, permissions, validation, or spend limits.
- [ ] Changes pricing, plan limits, or anything a user is charged.
Contributor guide
Research direction
Start with the address maps in protocols/wrapped.ts, protocols/pyth.ts, and protocols/layerzero.ts, then read protocol-pyth.test.ts, protocol-wrapped.test.ts, and the related docs in docs/plugins/. Check #2504 and #2506 before working on the dependent entries. Done means chain 999 is represented with the verified addresses, chain-set tests and calldata goldens pass, and the three protocol docs list HyperEVM.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- blockchain, typescript
- Domain
- blockchain, documentation, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100