Skip to content

feat(morpho): wire marketId-based supply/borrow + supplyCollateral - #22

Merged
Hiksang merged 1 commit into
mainfrom
feat/morpho-market-id-wiring
May 7, 2026
Merged

feat(morpho): wire marketId-based supply/borrow + supplyCollateral#22
Hiksang merged 1 commit into
mainfrom
feat/morpho-market-id-wiring

Conversation

@Hiksang

@Hiksang Hiksang commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Pre-fix every direct Morpho Blue tx (supply / borrow / repay / withdraw) silently sent all-zero MarketParams (defaultMarketParams(asset) stub) and reverted on-chain. The MetaMorpho-vault path (Felix on HyperEVM) worked, but the underlying Morpho Blue market path was 100% broken.

The Morpho API (https://blue-api.morpho.org/graphql) confirms many real markets exist on Monad (143) and HyperEVM (999). E.g. Monad's WMON/AUSD market at marketId 0xfa0b720389b546fcf8562c18cda8c00460072b63776add7fbfe8cd4f06d7c3ba (loan=AUSD, collateral=WMON, lltv=77%). The user just needs a way to point the adapter at a known marketId.

Reference: https://docs.morpho.org/get-started/resources/contracts/morpho/

Fix

Layer Change
defi-core/types.ts New MorphoMarketId type. Optional market_id? on Supply/Borrow/Repay/Withdraw params (Aave V3 / Compound ignore). New SupplyCollateralParams / WithdrawCollateralParams (market_id required).
defi-core/traits/lending.ts Optional buildSupplyCollateral? / buildWithdrawCollateral? — Aave V3 leaves them undefined (it collapses both into supply/withdraw); Morpho Blue requires the dedicated selectors.
defi-protocols/lending/morpho.ts New private resolveMarketParams(id) reads Morpho.idToMarketParams(id) over RPC; throws on zero-init tuples. supply/borrow/repay/withdraw all dispatch on market_id. New buildSupplyCollateral + buildWithdrawCollateral. ABI gains supplyCollateral + withdrawCollateral. Dead defaultMarketParams removed.
defi-cli/commands/lending.ts --market <marketId> on the four state-changing commands. New lending supply-collateral + lending withdraw-collateral.

Test plan

  • 8 new unit tests in morpho.test.ts:

    • supply / borrow / repay / withdraw with marketId — calldata decode against the resolved AUSD/WMON/oracle/irm/lltv tuple.
    • supplyCollateral / withdrawCollateral happy paths.
    • borrow without marketId → DefiError("marketId").
    • idToMarketParams returning zero-init → DefiError("empty MarketParams").

    All 8 use vi.mock(viem) so createPublicClient.readContract returns the canonical AUSD/WMON tuple; offline + deterministic.

  • pnpm -C ts -r build — clean.

  • pnpm -C ts -r lint — 3 packages, tsc --noEmit clean.

  • pnpm -C ts -r test — defi-core 32/32, defi-protocols 55/55 (was 47; +8), defi-cli 102/102.

  • Live mainnet broadcast on Monad WMON/AUSD market — deferred (requires WMON wrap from native MON first; defi token surface doesn't expose WMON.deposit() yet).

Out of scope

  • Per-chain marketIds TOML registry (so users don't have to look them up via the API).
  • Native MON → WMON wrap CLI (would close the loop on Monad Morpho lifecycle).
  • Compound V2 enterMarkets (Venus borrow) — different adapter limitation.

🤖 Generated with Claude Code

Pre-fix: every direct Morpho Blue tx (supply/borrow/repay/withdraw)
called `defaultMarketParams(asset)` which returned all-zero
MarketParams. This stub silently sent garbage on-chain — every
broadcast reverted because the (loanToken=0, collateralToken=0,
oracle=0, irm=0, lltv=0) tuple does not match any registered
market. The MetaMorpho-vault path (Felix on HyperEVM) worked, but
the underlying Morpho Blue market path was 100% broken.

Reference: https://docs.morpho.org/get-started/resources/contracts/morpho/

The Morpho API confirms many real markets exist on Monad (143) and
HyperEVM (999) — e.g. Monad's WMON/AUSD market at marketId
0xfa0b720389b546fcf8562c18cda8c00460072b63776add7fbfe8cd4f06d7c3ba
(loan=AUSD, collateral=WMON, lltv=77%). The user just needs a way
to point the adapter at a known marketId.

Fix:

1. defi-core/types.ts:
   - Add MorphoMarketId = `0x${string}` for clarity.
   - Add optional `market_id?` to SupplyParams / BorrowParams /
     RepayParams / WithdrawParams. Aave V3 / Compound adapters ignore
     it; the Morpho adapter dispatches on it.
   - Add SupplyCollateralParams / WithdrawCollateralParams (market_id
     required) for the Morpho-only collateral side.

2. defi-core/traits/lending.ts:
   - Optional `buildSupplyCollateral?` / `buildWithdrawCollateral?`
     (Aave V3 collapses both into supply/withdraw, so it leaves them
     undefined; Morpho Blue requires the dedicated selectors.)

3. defi-protocols/lending/morpho.ts:
   - New private `resolveMarketParams(marketId)` — reads
     Morpho.idToMarketParams(id) over the configured RPC and returns
     the 5-field tuple. Throws DefiError if the result is empty
     (zero-init guard) so users see a clear error instead of an
     opaque on-chain revert when they pass a marketId the deployment
     doesn't know about.
   - buildSupply: when `market_id` is set, route to direct
     Morpho.supply with the resolved tuple + approvals[]. Without
     market_id and without a registered MetaMorpho vault, throw
     instead of falling through to the old zero-stub.
   - buildBorrow / buildRepay: require market_id (Morpho Blue has no
     non-market borrow surface). Approvals[] for repay only.
   - buildWithdraw: prefer market_id if set, otherwise the
     MetaMorpho-vault branch, otherwise throw.
   - New buildSupplyCollateral / buildWithdrawCollateral methods
     using the dedicated Morpho selectors.
   - ABI gains supplyCollateral + withdrawCollateral entries.
   - Removed the dead `defaultMarketParams` helper.

4. defi-cli/commands/lending.ts:
   - All four state-changing commands gain `--market <marketId>`
     (passed through as `market_id` to the adapter). Aave V3 /
     Compound users can ignore it.
   - New CLI commands `lending supply-collateral` and
     `lending withdraw-collateral`. Each requires `--market` and
     emits a clear error envelope when the resolved adapter doesn't
     implement the corresponding optional method (e.g. Aave V3).

Test plan:

  - 8 new unit tests in morpho.test.ts:
    * supply/borrow/repay/withdraw with marketId — calldata decode
      against the resolved AUSD/WMON/oracle/irm/lltv tuple.
    * supplyCollateral / withdrawCollateral happy paths.
    * borrow without marketId → DefiError("marketId").
    * idToMarketParams returning zero-init → DefiError("empty
      MarketParams").

  - vi.mock(viem) so createPublicClient.readContract returns the
    canonical AUSD/WMON tuple; tests run offline.

Verified:
  - pnpm -C ts -r build  — clean.
  - pnpm -C ts -r lint   — 3 packages, tsc --noEmit clean.
  - pnpm -C ts -r test   — defi-core 32/32, defi-protocols 55/55
                           (was 47; +8), defi-cli 102/102.

Out of scope:
  - Live mainnet borrow lifecycle on Monad (requires WMON wrap from
    native MON first; the `defi token` surface doesn't expose
    WMON.deposit() yet).
  - Per-chain marketIds TOML registry (so users don't have to look
    them up). Tracked as a follow-up.
  - Compound V2 enterMarkets (Venus) — separate adapter limitation.
@Hiksang
Hiksang merged commit ce157cb into main May 7, 2026
4 checks passed
@Hiksang
Hiksang deleted the feat/morpho-market-id-wiring branch May 7, 2026 07:53
Hiksang added a commit that referenced this pull request May 8, 2026
…ion + adapter fixes (#32)

* build: regenerate dist + sync prebuild package configs for v1.0.13

Re-runs `pnpm -r build` from current main HEAD and syncs the package-
local config mirror so npm-published artifacts match the source tree.

Reason: across PRs #23 / #26 / #27 we resolved dist conflict markers
by taking main's stale dist (`git checkout --theirs`) — that kept the
PRs mergeable but left committed dist files out of sync with the
post-merge source. This single rebuild commit reconciles them.

Affected:
  defi-cli/dist/{index,main,mcp-server}.js  (~+700 lines source-derived)
  defi-cli/dist/*.map
  defi-protocols/dist/{index.js, index.d.ts, *.map}
  defi-core/dist/{index.d.ts, *.map}
  defi-cli/config/protocols/lending/venus_flux_bnb.toml
    (prebuild sync from workspace root: vusdt → vlisusd rename)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Release v1.0.13: bridge feature + production-grade verification + adapter fixes

Highlights since v1.0.12 (30 commits across 8 PRs):

== Bridge feature complete ==
- Relay added as 4th provider (~3s native bridge, executor-wired live)
- LiFi + deBridge + CCTP wired to Executor — `--broadcast` no longer silently ignored
- CCTP V2 `--auto-receive` — burn → poll Iris attestation → auto-receiveMessage on dest
- Live-verified Base→Arbitrum 0.1 USDC E2E (burn 0x6b5d41fd, receive 0x9b5bd5c2)
- Per-chain src/dst token resolution for cross-chain stables
- Relay errorCode parsing → actionable hints (AMOUNT_TOO_LOW, NO_QUOTES, etc)
- MCP `defi_bridge` 3 latent fixes: dst-token, Relay symbol→native, LiFi fromAddress

== Adapter bug fixes ==
- compound_v2: uint256.max withdraw + outstanding-borrow guard (Venus, Compound V2 forks)
- venus-flux-bnb: rename mislabeled `vusdt` → `vlisusd` (actual underlying is lisUSD)
- Ramses CL mint encoding + MerchantMoe/TraderJoe LB token-order realign
- Curve target the pool not router + StableswapNG dynamic uint256[] ABI
- uniswap-v2 / solidly LP-token approval on remove
- Hybra V4 --redeem-type CLI flag for instant-exit vs 2-year veHYBR lock
- Aave V3 collateral toggle + eMode for borrow lifecycle (#21)
- Morpho marketId-based supply/borrow + supplyCollateral (#22)
- Compound V2 enterMarkets toggle for Venus borrow lifecycle (#26)
- Native wrap/unwrap CLI + Morpho marketId registry + max-repay-by-shares (#27)

== Production-grade verification sweep ==
- HyperEVM 11/11 protocols full lifecycle (emission tokens received)
- Mantle: Aave V3 + UniV3 + MerchantMoe LB (MOE 1.009 received)
- Base: UniV3 + Aerodrome V2 + Aerodrome CL (AERO emission received)
- BNB upgraded to 🟢 production: 13/16 protocols verified live
- Monad partial: UniV3 + UniV2 (mainnet TBD for the rest)
- ve(3,3) emission tokens live-received: RAM, KITTEN, MOE, AERO, THE
- ULTRAQA sandbox script (51-test sweep across 4 chains)

== Hardening ==
- ip-address >=10.1.1 via pnpm.overrides (GHSA-v2v4-37r5-5v8g moderate XSS)
- viem wallet/public clients anchored to chainId at construction (#29)
- CLI handler unit-test coverage for status/schema (#28)
- @vitest/coverage-v8 baseline measurement script (#30)
- post-baseline test-foundation QA report (#31)

== Test plan ==
- pnpm -r test: 189/189 pass (defi-core 32, defi-protocols 55, defi-cli 102)
- pnpm -r build: clean across all 3 packages
- pnpm audit: 0 vulnerabilities
- ULTRAQA sandbox: 51/51 pass + 8/8 ve(3,3) lp pipeline
- CCTP --auto-receive E2E: live broadcast verified
- MCP defi_bridge smoke: LiFi USDC + Relay USDC + Relay native — all OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang added a commit that referenced this pull request May 16, 2026
Closes 5 Dependabot alerts on ts/pnpm-lock.yaml:

  high    fast-uri  3.1.0  -> 3.1.2  (#19 path traversal, #23 host confusion)
  medium  hono      4.12.16 -> 4.12.19 (#22 CSS injection in JSX SSR)
  medium  hono      4.12.16 -> 4.12.19 (#20 cache leak: Vary ignored)
  low     hono      4.12.16 -> 4.12.19 (#21 JWT NumericDate validation)

Both are transitive via @modelcontextprotocol/sdk:
  fast-uri  : @modelcontextprotocol/sdk -> ajv -> fast-uri
  hono      : @modelcontextprotocol/sdk -> {hono, @hono/node-server -> hono}

Existing hono override (>=4.12.12) was below the fix line; bumped to
>=4.12.18. fast-uri had no override; added >=3.1.2.

Verified: pnpm install + pnpm -r build + pnpm -r test all green
(defi-core 49 / defi-protocols 147 / defi-cli 235 = 431 passed).
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