Skip to content

feat(aave-v3): expose collateral toggle + eMode for borrow lifecycle - #21

Merged
Hiksang merged 1 commit into
mainfrom
feat/aave-v3-collateral-toggles
May 7, 2026
Merged

feat(aave-v3): expose collateral toggle + eMode for borrow lifecycle#21
Hiksang merged 1 commit into
mainfrom
feat/aave-v3-collateral-toggles

Conversation

@Hiksang

@Hiksang Hiksang commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

defi --chain X lending borrow ... reverted on every Aave V3 fork (Mantle, Base, BNB) because the adapter never emitted the two Pool toggles that gate borrowing against isolation-mode reserves:

  • setUserUseReserveAsCollateral(asset, useAsCollateral)
  • setUserEMode(uint8 categoryId)

Live evidence (prior session, 2026-05-07):

Chain tx gas revert
Mantle 0xdbec4213… USDT borrow 60k validateBorrow (collateral=0)
Base 0x1df2c4f2… WETH borrow 88k custom error 0x6d305815

The user had supplied USDC but never explicitly enabled it as collateral, so getUserAccountData reported totalCollateralBase = 0. Aave V3 docs confirm this is expected for isolation-mode reserves: https://aave.com/docs/aave-v3/smart-contracts/pool

Fix

File Change
defi-core/src/traits/lending.ts Optional buildSetUseReserveAsCollateral?(asset, useAsCollateral) + buildSetEMode?(categoryId)
defi-protocols/src/lending/aave_v3.ts POOL_ABI gains both functions verbatim from docs. New methods on AaveV3Adapter.
defi-cli/src/commands/lending.ts New CLI toggle-collateral (--enable / --disable) and set-emode (--category-id 0..255)

The trait additions are optional so non-Aave adapters (Compound V2 enterMarkets, Morpho Blue per-market authorize) stay flexible. The CLI checks typeof adapter.buildSetUseReserveAsCollateral === "function" and emits a clear error envelope if the resolved adapter doesn't implement it.

Live verification — BNB Aave V3 full borrow lifecycle (5/5 confirmed)

This is the first complete borrow lifecycle ever verified on this codebase.

Step tx block gas
supply USDC 1 0x5b4481dc… 96,854,843 170k
toggle-collateral USDC --enable 0x1c078498… 96,854,874 45k
borrow USDT 0.1 (variable) 0xb7b34740… 96,854,907 275k
repay max 0xe294a035… 96,854,965 157k
withdraw max 0xf5ef3065… 96,854,996 168k

Net wallet impact: ~$0.30 of gas. Principal recovered.

Per-chain status

Chain Aave V3 borrow status
BNB ✅ Live verified post-fix
HyperEVM ✅ Same adapter, fix applies (HyperLend / HypurrFi)
Mantle ⚠️ USDC LTV=0 in this deployment — toggle emits correct calldata but Pool's "LTV=0 cannot be enabled" guard rejects. Out of scope; tracked as separate per-chain config workaround.
Base ⚠️ WETH reserve is frozen=true ltv=0. USDC borrow (or non-frozen non-zero-LTV asset) would work; not retried in this PR.

Test plan

  • aave_v3.test.ts grew from 5 → 9 tests (+4 covering both toggle methods both directions + eMode opt-out description).
  • 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 47/47 (was 43; +4), defi-cli 102/102.
  • Live BNB borrow lifecycle: 5/5 confirmed.
  • Reviewer: spot-check selectors. setUserUseReserveAsCollateral should encode to selector starting with the keccak256-derived 4 bytes; setUserEMode(uint8) should likewise. The decode-roundtrip tests pin the args, but a reviewer can sanity-check the on-chain bytecode if desired.

Out of scope (DEF-A2 sub-items)

  • Compound V2 enterMarkets toggle (Venus).
  • Morpho Blue market discovery for Felix (HyperEVM) and Morpho-Blue Monad.
  • Compound V3 borrow verification on Base.

🤖 Generated with Claude Code

Pre-fix: `defi --chain X lending borrow ...` reverted on Aave V3 forks
(Mantle, Base, BNB) because the adapter never emitted the two Pool
toggles that gate borrowing against isolation-mode reserves:
  - setUserUseReserveAsCollateral(asset, useAsCollateral)
  - setUserEMode(uint8 categoryId)

Live evidence captured 2026-05-07 in the prior session:
  Mantle USDT borrow tx 0xdbec4213…  revert (gas 60k)
  Base   WETH borrow tx 0x1df2c4f2…  revert (custom error 0x6d305815)

Both reverts trace to the Pool's `validateBorrow` checks against the
user's collateral configuration — the user had supplied USDC but had
never explicitly enabled it as collateral, so total_collateral_usd
read 0 in `getUserAccountData`. Aave V3 docs (link below) confirm
this is the expected behaviour for isolation-mode reserves.

Reference: https://aave.com/docs/aave-v3/smart-contracts/pool

Fix:
  1. ILending trait (defi-core/traits/lending.ts):
     - Optional `buildSetUseReserveAsCollateral?(asset, useAsCollateral)`
     - Optional `buildSetEMode?(categoryId)`
     - Optional so non-Aave adapters (Compound V2 enterMarkets, Morpho
       Blue authorize) stay flexible.

  2. AaveV3Adapter (defi-protocols/lending/aave_v3.ts):
     - POOL_ABI gains the two functions verbatim from the docs.
     - buildSetUseReserveAsCollateral encodes (asset, bool) and
       targets the Pool. Returns a vanilla DeFiTx with no approvals
       (toggle is a permissionless user-state mutation).
     - buildSetEMode encodes uint8 categoryId; description marks
       categoryId=0 as "opt out" for log clarity.

  3. CLI (defi-cli/commands/lending.ts):
     - `defi lending toggle-collateral --protocol X --asset Y
        --enable | --disable [--broadcast]`
       Mutually-exclusive --enable/--disable; rejects with a clear
       error envelope if neither (or both) are passed, or if the
       resolved adapter doesn't implement the toggle (Compound V2,
       Morpho — explicit message points at the right migration path).
     - `defi lending set-emode --protocol X --category-id <0..255>
        [--broadcast]`
       Validates the integer range; rejects out-of-range with a
       clean error.

Live verification post-fix on BNB Aave V3 (full borrow lifecycle that
was 0% verified across all chains pre-fix):
  supply USDC 1     0x5b4481dc…  block 96,854,843
  toggle USDC       0x1c078498…  block 96,854,874  (gas 45,743)
  borrow USDT 0.1   0xb7b34740…  block 96,854,907  (gas 275,489)
  repay max         0xe294a035…  block 96,854,965  (gas 157,422)
  withdraw max      0xf5ef3065…  block 96,854,996  (gas 168,480)
All 5 confirmed; net wallet impact = ~$0.30 of gas, principal
returned.

Other chains' borrow paths still depend on per-chain Aave V3 reserve
config:
  - Mantle: USDC ltv may be 0 in this deployment — toggle still
    emits the right calldata but the Pool guard rejects (LTV=0
    cannot be enabled). Tracked separately; this PR ships the toggle
    primitive, not Mantle-specific config workarounds.
  - Base: WETH reserve is `frozen=true ltv=0` so borrowing it is
    impossible regardless of toggles. USDC borrow (or other
    non-frozen non-zero-LTV asset) would work; not retried in this
    session.

Test plan:
  - aave_v3.test.ts grew from 5 to 9 tests (+4 covering both toggle
    methods, both directions, and the eMode opt-out description).
  - 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 47/47
                           (was 43; +4), defi-cli 102/102 (unchanged).
  - Live BNB lifecycle (above): 5/5 confirmed.

Out of scope:
  - Compound V2 enterMarkets toggle (different ILending optional
    method and different protocol semantics; tracked as DEF-A2 sub).
  - Morpho Blue market discovery for Monad (DEF-A2).
  - Compound V3 borrow verification on Base (untouched in this PR).
@Hiksang
Hiksang merged commit f3f2310 into main May 7, 2026
4 checks passed
@Hiksang
Hiksang deleted the feat/aave-v3-collateral-toggles branch May 7, 2026 07:27
Hiksang added a commit that referenced this pull request May 8, 2026
…ow lifecycle (#26)

Compound V2 family (Venus on BNB, Compound on Mainnet etc.) silently rejects
borrows when the supplied cToken hasn't been entered as collateral via
Comptroller.enterMarkets([cToken]). Without that call, getAccountLiquidity
reports zero collateral and any borrow reverts. Mirrors the role of Aave V3's
setUserUseReserveAsCollateral (PR #21), but the API is batch-by-cToken.

Changes:
- compound_v2.ts: add COMPTROLLER_ABI (enterMarkets/exitMarket), comptroller
  field, and buildEnterMarkets(cTokens[]) method on CompoundV2Adapter.
- ILending: add optional buildEnterMarkets?(cTokens) for Compound-V2-family
  adapters. Aave V3 / Compound V3 / Morpho leave this undefined.
- defi lending enter-markets: new CLI subcommand resolving --asset to the
  registered v* contract via symbol prefix matching (e.g. USDT → vusdt).
- compound_v2.test.ts: 4 new regressions pinning calldata + comptroller
  target, batch enter, empty-array guard, missing-comptroller guard.

Live BNB Venus lifecycle (5/5 confirmed on bsc mainnet):
- supply  0.05 USDT  → 0x687ece09ff808f61dac7b4adef732baf68e1acd362cc89af8b05f8890b3f26c5
- enter   vUSDT      → 0x21ce1f63d062f7e2c64fffdc5a8bb2ee0417b3a2b964e0f0cdc3e1610f002d2d
- borrow  0.02 USDT  → 0x0b9c7d3e5fcd9161c1888177b1d2b882d50cbd19ab134ed512ad4ddc1b966857
- repay   0.02 USDT  → 0xe1e98194fd0cd33ca6f39333d02c8787c3507e4187102348f0a0b4d29029e906
- withdraw 0.05 USDT → 0x9450acb409d41927e710a1494e097c5980c0ef754609381b946c448dbaf56daf

Borrow tx confirms enter-markets did register vUSDT in accountAssets[]; the
exact same flow without the toggle is what was reverting pre-PR.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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