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

@julien-devatom julien-devatom commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Status: mainnet API is live — unblocked

The earlier blocker is cleared. api.morpho.org now serves chain 1 across every endpoint family
this bot uses, verified against live data:

Endpoint chain 1 result
/v0/midnight/markets?chain_ids=1 200 — 1 market (USDC, maturity 1787929200)
/v0/midnight/users/{maker}/offer-groups?chain_ids=1 200
/v0/midnight/books/{id}/asks/takeable-offers 200 — live offers, chain_id: 1
/v0/midnight/books/{id}/bids/takeable-offers 200 — live offers, chain_id: 1

The returned offers carry midnight: 0x471686c4…2045f, matching the singleton registered here.

One operational caveat, not a code issue: that market is not listed=true yet
(markets?chain_ids=1&listed=true returns an empty set). getBook gates on the listing allowlist,
so until it is listed, setup-check will report not allowlisted for it. Nothing to change in this
PR — it just needs the market listed API-side before a mainnet run goes green.

Follow-up: the local address shim is now redundant

@morpho-org/morpho-ts@2.10.0 ships mainnet Midnight addresses, and all four match this PR
exactly
(it also adds ecrecoverAuthorizer, midnightBundles, and
midnightBlueBuyCallbackFactory). The registerCustomAddresses shim in
supported-chains.utils.ts can therefore be deleted — its own JSDoc says as much.

I deliberately did not do it here, because bumping morpho-ts alone is not safe:

  • we pin morpho-ts 2.8.0; bumping only that pin resolves quoter-bot and blue-sdk to 2.10.0
    while morpho-sdk@5.4.1 stays on 2.8.0two instances;
  • getChainAddress reads a module-level registry, and morpho.midnight(chainId) resolves
    addresses lazily through morpho-sdk's own instance, so it would read the copy with no mainnet
    Midnight entries and throw UnknownAddressError at runtime — a silent mainnet break that the
    current single-instance 2.8.0 setup does not have;
  • pnpm dedupe and an overrides entry both failed to collapse the two.

The real fix is a coordinated stack bump — morpho-sdk@5.7.0 (which requires morpho-ts ^2.10.0),
blue-sdk ^6.6.0, blue-sdk-viem ^5.4.0 — which touches all six bots and is exactly the kind of
change pnpm-workspace.yaml says should be "a separate, deliberate bump". Worth its own PR; this
one stays on the pinned stack and keeps working either way.

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.

Mainnet Midnight addresses are registered through the SDK's own registerCustomAddresses, because
the pinned registry carries Midnight entries for Base only. 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 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 independently verified before being
committed — and have since been confirmed against morpho-ts@2.10.0:

  • 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.

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