Skip to content

feat(quoter-bot): add ethereum mainnet support - #173

Draft
julien-devatom wants to merge 1 commit into
mainfrom
ethereum-mainnet-quoter-073b6a
Draft

feat(quoter-bot): add ethereum mainnet support#173
julien-devatom wants to merge 1 commit into
mainfrom
ethereum-mainnet-quoter-073b6a

Conversation

@julien-devatom

Copy link
Copy Markdown
Collaborator

Blocked on mainnet API support — do not merge yet

This is intentionally a draft. The code is complete and verified, but the bot cannot actually
run on mainnet until the Morpho API serves chain 1.

api.morpho.org currently rejects mainnet on every /v0/midnight/* endpoint:

GET /v0/midnight/markets?chain_ids=1
{"error":{"code":"VALIDATION_FAILED","details":[{"message":"Unsupported chain_id. Supported chain_ids: 8453"}]}}

The quoter bot reads four Morpho API endpoint families — markets, offer-groups, books
takeable-offers, and prospective-offer validation — so correct contract addresses alone are not
enough. With CHAIN_ID=1 against the public API, the bot passes its on-chain checks and then fails
at the books/markets stage. MORPHO_API_BASE_URL is configurable, so an origin that serves chain-1
Midnight data unblocks this without further code changes.

Why

Midnight is deployed on Ethereum mainnet, but the bot was pinned to Base in three ways: a
BASE_CHAIN_ID constant, viem/chains base imports, and hardcoded chain_ids=8453 API filters.
This threads the configured chain through every client, address lookup, and API query so CHAIN_ID
accepts 1 alongside 8453.

Two upstream sources still know only about Base, and this PR works around both:

  • The SDK address registry. @morpho-org/morpho-ts carries Midnight entries for Base only —
    confirmed unchanged in the latest 2.9.0, not just our pinned 2.8.0. Mainnet addresses are
    registered through the SDK's own registerCustomAddresses. That call merges rather than
    replaces, so chain 1 keeps its upstream Morpho Blue entries (42 → 46 keys); there is a regression
    test pinning this, because a replacing registration would silently break the reference-rate reads.
  • The API, as above — not solvable in this repo.

The non-obvious bug this avoids

viem-setup-state.utils.ts pinned keccak256 hashes of the Base ratifier bytecode as a safety
gate. Ratifier runtime embeds its immutable Midnight target, so mainnet's hashes necessarily differ
— the old constants would have failed every mainnet setup check. These are now chain-keyed, and
live beside the addresses so they get updated together.

The mainnet hashes were read from live bytecode with the Base hashes recomputed in the same pass as
a control, reproducing the existing pinned constants byte-for-byte.

Separately, supportedChain() installs the address shim rather than leaving it to chainAddress():
the SDK's morpho.midnight(chainId) extension stores only { client, chainId } and resolves
addresses lazily per call, so it would otherwise bypass the wrapper entirely.

Address provenance

Mainnet addresses were supplied by @julien-devatom and verified before being committed:

  • all hold deployed bytecode on mainnet;
  • both ratifiers' MIDNIGHT() returns exactly the singleton in this PR (same-deployment proof),
    with Base checked the same way as a passing control;
  • configurator is the same address on both chains.

ecrecoverAuthorizer was also supplied but is deliberately not registered — the quoter bot never
reads it.

Verification

  • typecheck 0 errors; lint 0 errors / 0 warnings; format clean; jsdoc:check and jsdoc:build exit 0
  • 2140 tests pass, 0 failures, including 11 new tests for the chain module and new cross-chain
    ratifier-rejection tests
  • new tests confirmed non-vacuous by breaking the source constant and observing the failure
  • end-to-end config load: CHAIN_ID=1 resolves the mainnet mempool while keeping chain-1 Blue
    intact, 8453 is unchanged, 10 is rejected

Pre-existing and untouched: the quoter-bot and midnight-liquidation e2e fork suites need
RPC_URL_8453 and remain Base-pinned. Porting them needs a mainnet fork URL, so they were left
alone rather than half-converted.

🤖 Generated with Claude Code

Thread the configured chain through every client, address lookup, and
Morpho API query so CHAIN_ID accepts 1 (Ethereum mainnet) alongside 8453
(Base). Previously the chain was pinned by a BASE_CHAIN_ID constant,
viem/chains `base` imports, and hardcoded `chain_ids=8453` filters.

Per-chain deployment facts now live in one seam,
config/supported-chains.utils.ts:

- Midnight mainnet addresses are registered through the SDK's own
  registerCustomAddresses, because the pinned morpho-ts registry carries
  Midnight entries for Base only (verified unchanged in latest 2.9.0).
  Registration merges rather than replaces, so chain 1 keeps its upstream
  Morpho Blue entries, and is driven from the accessors instead of module
  evaluation so no call site depends on import order.
- Ratifier runtime-bytecode hashes become chain-keyed. Ratifier runtime
  embeds its immutable Midnight target, so the previously pinned Base
  hashes would have failed every mainnet setup check. Mainnet hashes were
  read from live bytecode with the Base hashes recomputed as a control,
  reproducing the existing constants exactly.

supportedChain() also installs the address shim: the SDK's
morpho.midnight(chainId) extension stores only { client, chainId } and
resolves addresses lazily per call, so it would otherwise bypass the
chainAddress wrapper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@julien-devatom julien-devatom self-assigned this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant