feat(quoter-bot): add ethereum mainnet support - #173
Conversation
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>
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>
|
@prd-carapulse[bot] can you review and approve if all good |
|
Review posted on PR #173. • Code review: No blocking correctness, security, or coverage issue found at head • Checks: • Approval: Withheld until required checks finish. • Local tests: Blocked by Node mismatch: repo requires Review comment: #173 (review) |
|
@codex review |
There was a problem hiding this comment.
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.
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>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Reviewed PR #173 at head ✅ No blocking correctness, security, or coverage issues found. Posted review comment on GitHub. |
There was a problem hiding this comment.
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.
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>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Review posted on PR #173 at head • Code: No blocking correctness, security, or coverage issue found. |
There was a problem hiding this comment.
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.
Test check is still in progress. Approve after the required check completes.
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>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Review posted on PR #173 at head ✅ No blocking correctness, security, coverage, or documentation issues found. Review: #173 (review) |
There was a problem hiding this comment.
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.
Local reproduction is blocked by the repository engine requirement (^24.14.1); this environment has Node v26.3.0.
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>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Reviewed PR #173 at head ✅ No blocking correctness, security, or coverage issues found. Posted formal review: |
There was a problem hiding this comment.
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.
Test check is still in progress. Approve after the check completes successfully.
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
|
@prd-carapulse[bot] can you review and approve if all good |
|
PR #173 reviewed and approved. • Head: |
There was a problem hiding this comment.
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.
|
Reviewed current head ✅ No blocking correctness, security, or test-coverage issue found. I did not merge the PR. |
There was a problem hiding this comment.
💡 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".
| /** Reads the archive provider's chain identifier. @returns The chain the reference provider is actually serving. */ | ||
| getReferenceChainId(): Promise<number> |
There was a problem hiding this comment.
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 => |
There was a problem hiding this comment.
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 👍 / 👎.
Status: mainnet API is live — unblocked
api.morpho.orgnow serves chain 1 across every endpoint family this bot uses, verified againstlive data:
/v0/midnight/markets?chain_ids=1200— 1 market (USDC, maturity 1787929200)/v0/midnight/users/{maker}/offer-groups?chain_ids=1200/v0/midnight/books/{id}/asks/takeable-offers200— live offers,chain_id: 1/v0/midnight/books/{id}/bids/takeable-offers200— live offers,chain_id: 1The returned offers carry
midnight: 0x471686c4…2045f, matching the singleton this bot resolves.One operational caveat, not a code issue: that market is not
listed=trueyet(
markets?chain_ids=1&listed=truereturns an empty set).getBookgates on the listing allowlist,so until it is listed,
setup-checkwill reportnot allowlistedfor it.Why
Midnight is deployed on Ethereum mainnet, but the bot was pinned to Base in three ways: a
BASE_CHAIN_IDconstant,viem/chainsbaseimports, and hardcodedchain_ids=8453API filters.This threads the configured chain through every client, address lookup, and API query so
CHAIN_IDaccepts
1alongside8453.Addresses come from the SDK
@morpho-org/morpho-ts@2.10.0ships the mainnet Midnight addresses, so this PR reads them throughthe SDK's
getChainAddress— there is no local address table and noregisterCustomAddressesshim.
Getting there required bumping the stack together rather than
morpho-tsalone. Bumping onlymorpho-tsleaves two instances — quoter-bot andblue-sdkon2.10.0,morpho-sdk@5.4.1still on
2.8.0. SincegetChainAddressreads a module-level registry andmorpho.midnight(chainId)resolves addresses lazily through morpho-sdk's own instance, that pathwould read the copy with no mainnet Midnight entries and throw
UnknownAddressErrorat runtime.So the bump is:
morpho-sdk5.4.1 → 5.7.0(it requiresmorpho-ts ^2.10.0), catalogblue-sdk6.5.0 → 6.7.0,blue-sdk-viem5.2.1 → 5.4.0,morpho-ts2.8.0 → 2.10.0. Verified through thepnpm symlink targets that exactly one
@morpho-org+morpho-ts@2.10.0instance remains — quoter-bot,morpho-sdk, and blue-sdk all point at it. The catalog bump also reaches
vault-v1-reallocationandvault-v2-reallocation, so those are covered by the full-suite run below.The non-obvious bug this avoids
viem-setup-state.utils.tspinned keccak256 hashes of the Base ratifier bytecode as a safetygate. 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.0exactly:MIDNIGHT()returns exactly the singleton this bot resolves (same-deploymentproof), with Base checked the same way as a passing control;
configuratoris the same address on both chains.Verification
pnpm -r run typecheck), not just quoter-botjsdoc:checkandjsdoc:buildexit 0test/viem-dedupe.test.tssingle-instance guard
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
CHAIN_ID=1resolves the mainnetmempool,
8453is unchanged,10is rejectedPre-existing and untouched: the quoter-bot and midnight-liquidation e2e fork suites need
RPC_URL_8453and remain Base-pinned. Porting them needs a mainnet fork URL, so they were leftalone rather than half-converted.
🤖 Generated with Claude Code