KeeperHub / KeeperHub/keeperhub

feat(protocols): add HyperEVM (chain 999) to the wrapped, Pyth and LayerZero protocols

Open
#2,510 1 comment 0 reactions 0 assignees View on GitHub
accepted contracts-checked enhancement hold
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.