Skip to content

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

Merged
julien-devatom merged 8 commits into
mainfrom
ethereum-mainnet-quoter-073b6a
Aug 31, 2026
Merged

feat(quoter-bot): add ethereum mainnet support#173
julien-devatom merged 8 commits 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

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 this bot resolves.

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.

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.

Addresses come from the SDK

@morpho-org/morpho-ts@2.10.0 ships the mainnet Midnight addresses, so this PR reads them through
the SDK's getChainAddress — there is no local address table and no registerCustomAddresses
shim
.

Getting there required bumping the stack together rather than morpho-ts alone. Bumping only
morpho-ts leaves two instances — quoter-bot and blue-sdk on 2.10.0, morpho-sdk@5.4.1
still on 2.8.0. Since getChainAddress reads a module-level registry and
morpho.midnight(chainId) resolves addresses lazily through morpho-sdk's own instance, that path
would read the copy with no mainnet Midnight entries and throw UnknownAddressError at runtime.

So the bump is: morpho-sdk 5.4.1 → 5.7.0 (it requires morpho-ts ^2.10.0), catalog blue-sdk
6.5.0 → 6.7.0, blue-sdk-viem 5.2.1 → 5.4.0, morpho-ts 2.8.0 → 2.10.0. Verified through the
pnpm symlink targets that exactly one @morpho-org+morpho-ts@2.10.0 instance remains — quoter-bot,
morpho-sdk, and blue-sdk all point at it. The catalog bump also reaches
vault-v1-reallocation and vault-v2-reallocation, so those are covered by the full-suite run below.

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.

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. These runtime hashes stay local
deliberately: the SDK registry carries deployment addresses, not bytecode hashes.

Address provenance

Mainnet addresses were supplied by @julien-devatom, independently verified, and then confirmed to
match morpho-ts@2.10.0 exactly:

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

Verification

  • typecheck clean across all workspace packages (pnpm -r run typecheck), not just quoter-bot
  • lint 0 errors / 0 warnings; format clean; jsdoc:check and jsdoc:build exit 0
  • 2140 tests pass, 0 failures, including the repo's own test/viem-dedupe.test.ts
    single-instance guard
  • 11 tests for the chain module — mainnet/Base address resolution now asserted straight against the
    SDK registry, so an SDK downgrade or upstream address change fails in CI rather than on a live
    chain — plus cross-chain ratifier-rejection tests
  • end-to-end config load with no registration anywhere: CHAIN_ID=1 resolves the mainnet
    mempool, 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
julien-devatom and others added 3 commits August 31, 2026 09:09
morpho-ts 2.10.0 ships the mainnet Midnight addresses, so the local
registerCustomAddresses shim is no longer needed. Remove
MAINNET_MIDNIGHT_ADDRESSES, ensureMidnightAddressesRegistered, and the
chainAddress wrapper, and read addresses through the SDK's getChainAddress
directly.

Bumping morpho-ts alone would have split it into two instances: quoter-bot
and blue-sdk on 2.10.0, morpho-sdk 5.4.1 still on 2.8.0. getChainAddress
reads a module-level registry and morpho.midnight(chainId) resolves
addresses lazily through morpho-sdk's own instance, so that path would have
read a copy without mainnet entries and thrown UnknownAddressError at
runtime. Bump the stack together instead — morpho-sdk 5.7.0, blue-sdk 6.7.0,
blue-sdk-viem 5.4.0 — leaving exactly one morpho-ts 2.10.0 instance,
verified through the pnpm symlink targets.

Per-chain ratifier runtime hashes stay local: the SDK registry carries
deployment addresses but not runtime bytecode hashes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The push of 5e0ef4d did not fire the pull_request synchronize event, so
Checks never ran for it. Empty commit to trigger CI; squashed at merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two conflicts, both resolved by keeping the intent of each side:

- src/index.ts: main added a monitoring logger to the observability record
  while this branch made the observability chain configurable. Resolve the
  chain once into a local and pass it to both the record and the logger, so
  they can never disagree; both were previously pinned to Base.
- README.md env-var table: main added the QUOTER_SIGNER_LAMBDA_ARN row and
  the `middleware` key-storage option, this branch rewrote the CHAIN_ID,
  RPC_URL, and REFERENCE_RPC_URL rows for multi-chain. Take main's table and
  re-apply only those three rows.

pnpm-lock.yaml auto-merged into a broken state — it still referenced
midnight-sdk peer variants resolved against morpho-ts 2.8.0, which this
branch no longer installs. Regenerated it; `pnpm install --frozen-lockfile`
now passes and exactly one morpho-ts 2.10.0 instance remains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@prd-carapulse[bot] can you review and approve if all good

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review posted on PR #173.

Code review: No blocking correctness, security, or coverage issue found at head 65814601f6cc1773057f090d6cec7ffe8e18673c.

Checks: Lint, Typecheck, and Dead-Code pass. Test and Notify remain pending.

Approval: Withheld until required checks finish.

Local tests: Blocked by Node mismatch: repo requires ^24.14.1; environment has v26.3.0.

Review comment: #173 (review)

@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@codex review

@prd-carapulse prd-carapulse Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 6581460. No blocking correctness, security, or test-coverage issue found in the chain-threading and SDK upgrade changes. Lint, Typecheck, and Dead-Code pass.

Withholding approval until the required Test check completes; it is still in progress. Local reproduction was blocked by the checkout requiring Node ^24.14.1 while this environment has v26.3.0.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Codex P1: createLadderGroupOwnership derived its state-file key from
{strategy, maker} with no chain. One maker running Base and mainnet against
a shared XDG_STATE_HOME — including switching CHAIN_ID in the local Compose
deployment — made each chain read the other's publications as removed
markets, submit cancellation transactions for group IDs that never existed
on that chain, and rewrite the shared file, so returning to the first chain
lost ownership evidence for still-live groups and failed readiness.

Include chainId in the strategy key. Only Base could be configured while the
chain-less key was in use, so state stored under it is Base state by
construction: Base adopts it as a legacy source and migrates it forward,
while every other chain ignores it rather than importing foreign
publications.

Bootstrap ownership is deliberately left alone: its key already includes the
configured marketIds, which are chain-specific, and it has no legacy
migration path, so re-keying it would orphan existing state for no gain.

Codex P2: docs/reference.md is the complete operator reference and still
documented CHAIN_ID as Base-only. Update its chain rows, setup checklist,
observability chainId, and remaining Base-specific prose to match README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@codex review

@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@prd-carapulse[bot] can you review and approve if all good

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Reviewed PR #173 at head e73f132c1803aab23f13dee858b11c9a53d10791.

✅ No blocking correctness, security, or coverage issues found.
Lint, Dead-Code, and Notify pass.
⚠️ Approval withheld: PR remains draft; Test and Typecheck are still pending.

Posted review comment on GitHub.

@prd-carapulse prd-carapulse Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head e73f132c1803aab23f13dee858b11c9a53d10791. No blocking correctness, security, or test-coverage issue found in the chain-threading, SDK upgrade, or chain-scoped ownership changes.

Approval withheld: PR is still draft, and required Test and Typecheck checks are pending. Lint, Dead-Code, and Notify pass.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Codex round two, four findings:

P1 — the archive provider's chain was never verified. SetupCheckService
checked only the current-state reader, and the reference reader exposed just
block and contract reads, so CHAIN_ID=1 paired with a stale Base
REFERENCE_RPC_URL passed readiness whenever the Blue reference market
resolved on both chains; the variable-rate strategy then derived quotes from
Base state and submitted them on mainnet. Expose getReferenceChainId, read it
only when a variable-rate strategy makes the archive load-bearing, and fold
it into the chain check as `referenceConnected`.

P2 — referenceLookbackBlocks defaulted to 10_800 blocks, which is the
six-hour rate window at Base's two-second cadence but about 36 hours at
Ethereum's twelve seconds, failing readiness for a reference market
initialized or funded inside that gap. Derive the default from viem's
blockTime; Base keeps 10,800 exactly and mainnet gets 1,800.

P2 — observability read CHAIN_ID from the environment alone, so selecting
mainnet through quoter-bot.yaml labeled every lifecycle and monitoring record
8453 while the bot ran chain 1. Resolve the label from the same sources
configuration reads, keeping the environment's precedence and never throwing.

P2 — docs/npm-README.md ships as the published package README and still
described the CLI as Base-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@codex review

@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@prd-carapulse[bot] can you review and approve if all good

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review posted on PR #173 at head ee780092560761bfcba6bb9fb75b75e953b82e6a.

Code: No blocking correctness, security, or coverage issue found.
Tests: Targeted quoter-bot tests pass: 155/155. Non-E2E/non-CLI suite passes: 1,117/1,117.
Checks: Typecheck, Lint, Dead-Code, and Format pass.
Approval: Withheld. PR remains draft; required Test check is still in progress.
Full-suite caveats: Failures are environment/pre-existing: missing @repo/contracts build output, absent RPC_URL_8453 for fork tests, and Node v26 DEP0205 warnings breaking five CLI assertions.
Changes: None needed; no follow-up PR opened.

@prd-carapulse prd-carapulse Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head ee780092560761bfcba6bb9fb75b75e953b82e6a.

✅ No blocking correctness, security, or coverage issue found in the chain-threading, chain-scoped ownership, archive-provider validation, and SDK upgrade changes.
✅ Targeted quoter-bot tests pass: 155/155.
✅ Quoter-bot non-E2E/non-CLI suite passes: 1,117/1,117.
✅ Typecheck, Lint, Dead-Code, and Format checks pass.
⚠️ Approval withheld: PR is still draft and required Test check is still in progress. Approve after the required check completes.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Codex round three, two findings:

Namespace bootstrap ownership by chain, mirroring the ladder fix. Its key
hashed only the maker and market IDs, leaving the store chain-ambiguous once
two chains are possible. This also answers the earlier reasoning for leaving
it alone: rather than skip it for lack of a migration path, give it one — the
chain-less key is Base state by construction, so Base reads it as a fallback
and the next write migrates it to the chain-scoped path, while other chains
never adopt it.

Accept the short `-c` spelling in the observability config pre-parser. The
CLI declares `-c, --config <path>`, so `morpho-quoter -c mainnet.yaml` was
resolving no path and mislabeling a mainnet run as Base. A dangling flag with
no following value is ignored rather than read as a path.

Both covered by regression tests verified non-vacuous against the un-fixed
source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@codex review

@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@prd-carapulse[bot] can you review and approve if all good

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review posted on PR #173 at head 8b06a072b6c35c49fa2857ffd83d60169419ab26.

✅ No blocking correctness, security, coverage, or documentation issues found.
Lint, Typecheck, and Dead-Code pass.
⚠️ Approval withheld: PR remains draft; required Test check is still running.
⚠️ Local reproduction blocked by Node requirement ^24.14.1; environment has v26.3.0.

Review: #173 (review)

@prd-carapulse prd-carapulse Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 8b06a07.

✅ No blocking correctness, security, or coverage issue found in the chain-threading, chain-scoped ownership, archive-provider validation, SDK upgrade, or documentation changes.
✅ Lint, Typecheck, and Dead-Code pass.
⚠️ Approval withheld: PR is still draft and required Test check is still running (unit tests in progress; browser smoke tests pending).

Local reproduction is blocked by the repository engine requirement (^24.14.1); this environment has Node v26.3.0.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Codex P1: the chain-scoped ladder key was not enough on its own. Legacy
discovery also adopts a file when `candidateStrategy` equals
`attributableLegacyStrategy`, which is rebuilt from the candidate file's own
market IDs and carries no chain — so an older market-scoped Base file
self-attributes on every chain, and mainnet would import Base publications
and cancel their groups.

Adopt a legacy file by key on Base only. Other chains still adopt one when a
group inside it is verified as owned on the chain being configured, which is
chain-safe because that verification queries the configured chain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@codex review

@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@prd-carapulse[bot] can you review and approve if all good

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Reviewed PR #173 at head 8d9ccdae49ac66691551a6481d8fac474611fe82.

✅ No blocking correctness, security, or coverage issues found.
Lint, Typecheck, and Dead-Code pass.
⚠️ Approval withheld: PR remains draft and required Test check is still in progress.

Posted formal review: PRR_kwDOSErDAM8AAAABLe6KEA. No code changes needed.

@prd-carapulse prd-carapulse Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 8d9ccdae49ac66691551a6481d8fac474611fe82.

✅ No blocking correctness, security, or coverage issue found in the chain threading, SDK upgrade, provider-chain validation, chain-scoped ownership migration, or observability labeling.
Lint, Typecheck, and Dead-Code pass.
⚠️ Approval withheld: PR is still draft and required Test check is still in progress. Approve after the check completes successfully.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 8d9ccdae49

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@julien-devatom
julien-devatom marked this pull request as ready for review August 31, 2026 12:26

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@julien-devatom

Copy link
Copy Markdown
Collaborator Author

@prd-carapulse[bot] can you review and approve if all good

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

PR #173 reviewed and approved.

Head: 8d9ccdae49ac66691551a6481d8fac474611fe82
Checks: Test, Typecheck, Lint, Dead-Code, Notify — all pass
Review: No blocking correctness, security, or test-coverage issues found
Merge: Not performed
Thread reply: #173 (comment)

@prd-carapulse prd-carapulse Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 8d9ccda.

✅ No blocking correctness, security, or test-coverage issue found in the chain-threading, chain-scoped ownership migration, provider validation, observability labeling, or SDK upgrade.
✅ Required checks pass: Test, Typecheck, Lint, Dead-Code, and Notify.

Approved. I did not merge the PR.

@prd-carapulse

prd-carapulse Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Reviewed current head 8d9ccdae49ac66691551a6481d8fac474611fe82.

✅ No blocking correctness, security, or test-coverage issue found.
✅ Required checks pass: Test, Typecheck, Lint, Dead-Code, and Notify.
✅ PR approved.

I did not merge the PR.

@julien-devatom
julien-devatom merged commit a9aa2e1 into main Aug 31, 2026
12 checks passed
@julien-devatom
julien-devatom deleted the ethereum-mainnet-quoter-073b6a branch August 31, 2026 12:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d9ccdae49

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +144 to +145
/** Reads the archive provider's chain identifier. @returns The chain the reference provider is actually serving. */
getReferenceChainId(): Promise<number>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Document failures on the reference-chain boundary

This new method belongs to the exported SetupStateService interface, but its JSDoc documents only the return value and omits that the archive-provider operation can reject and that it performs no writes. Add the applicable failure and side-effect contract here so consumers of the interface receive the substantive public-surface documentation required for changed TypeScript methods; the implementation-level comment does not document this exported declaration.

AGENTS.md reference: AGENTS.md:L35-L40

Useful? React with 👍 / 👎.

* @remarks Callers compare this against `keccak256` of fetched runtime code; a mismatch means the
* configured ratifier is not the canonical deployment for that chain and must be rejected.
*/
export const ratifierRuntimeHash = (chainId: SupportedChainId, type: 'ecrecover' | 'setter'): Hex =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the new config modules to TypeDoc

Neither src/config/supported-chains.utils.ts nor src/config/observability-chain.utils.ts was added to typedoc.json's explicit entryPoints, so jsdoc:build omits all of the new exported chain-selection APIs even though the AST coverage check discovers them. Add both boundary files to the TypeDoc entry list so the required browsable public documentation actually includes this new surface.

AGENTS.md reference: AGENTS.md:L35-L40

Useful? React with 👍 / 👎.

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