Skip to content

feat: add Moonwell lending provider (Base, Optimism)#46

Merged
ggonzalez94 merged 9 commits into
mainfrom
feat/add-provider-moonwell
Mar 20, 2026
Merged

feat: add Moonwell lending provider (Base, Optimism)#46
ggonzalez94 merged 9 commits into
mainfrom
feat/add-provider-moonwell

Conversation

@ggonzalez94
Copy link
Copy Markdown
Owner

@ggonzalez94 ggonzalez94 commented Mar 20, 2026

Summary

  • Add Moonwell as a new lending+yield provider on Base and Optimism with full read and execution support.
  • Markets, rates, positions (supply/collateral/borrow), yield opportunities, and yield positions read via on-chain RPC multicall batching (no API key required).
  • Execution planning for lend supply|withdraw|borrow|repay and yield deposit|withdraw using Compound v2-style mToken contracts.
  • Auto-resolve mToken by underlying asset via Comptroller.getAllMarkets() + Multicall3, or use --pool-address for explicit mToken.
  • --on-behalf-of properly rejected (Compound v2 operates on msg.sender only).
  • Alternate recipient properly rejected at planner level.
  • Optimism USDC bootstrap address corrected to native USDC (0x0b2c...ff85); bridged USDC.e added as separate entry.
  • --amount max support added (resolves to uint256.max for full-balance repay).
  • Contract addresses verified against official Moonwell docs.
  • Mintlify docs updated across all provider references.

Supersedes fork PR #36 (all original code preserved, with review fixes merged).

Security review notes

  • ABI encodings verified correct for mint/redeemUnderlying/borrow/repayBorrow
  • Multicall3 defensive guard against empty calls
  • All multicall sub-calls use AllowFailure=true, preventing RPC panics from malformed responses
  • Inputs validated: sender address, asset address, positive amount, recipient=sender enforcement
  • No path exists to drain funds via crafted inputs (all execution targets are the mToken contract for the resolved underlying asset)
  • Comptroller and RewardDistributor addresses verified against Moonwell official docs

Test plan

  • go test ./... passes
  • go vet ./... passes
  • go build ./... passes
  • Planner tests: supply (with approval+enterMarkets), withdraw, borrow, repay, alternate-recipient rejection, unsupported verb, auto-resolve via multicall
  • Client tests: markets, rates, positions (filtering by type), yield opportunities, yield positions, unsupported chain, rate-to-APY math
  • Registry tests: moonwell on-behalf-of rejection (lend + yield), provider normalization aliases
  • Contract addresses verified against official Moonwell documentation

🤖 Generated with Claude Code


Note

High Risk
High risk because it adds new on-chain RPC reading and transaction planning/execution paths (lend/yield) with new contract/ABI metadata; mistakes could mis-route funds or break execution on supported chains.

Overview
Adds Moonwell provider support across lend and yield, wiring it into provider routing and CLI flags/enums so Moonwell can be selected for markets/rates/positions/opportunities/positions and for plan|submit|status execution.

Implements Moonwell on-chain reads and execution planning: a new Moonwell provider client uses RPC + Multicall3 batching to compute markets/rates/positions/yield data, and a new planner builds Compound v2 mToken transactions (with optional auto-resolution of mToken via Comptroller.getAllMarkets()), explicitly rejecting unsupported semantics like --on-behalf-of and alternate recipients.

Updates shared plumbing and metadata: adds Moonwell contract addresses and ABIs to the registry, extends lend positions requests with an optional rpc_url, adds --amount max normalization to uint256.max, fixes Optimism USDC bootstrap mapping (and adds USDC.e), and updates README/CHANGELOG/Mintlify docs to reflect the new provider and caveats.

Written by Cursor Bugbot for commit d33f4bc. This will update automatically on new commits. Configure here.

imthatcarlos and others added 9 commits March 10, 2026 09:48
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove dead code: unused callGetAccountSnapshot function and
  abandoned priceUSD sentinel block in fetchMarkets
- Simplify price mantissa handling by storing it directly in the
  phase1Data struct instead of recovering via a separate loop
- Add --rpc-url flag to lend positions command so Moonwell (which
  reads from the chain via RPC) can use custom endpoints
- Fix --providers help text on yield opportunities and yield positions
  commands to include moonwell
- Fix --provider help text on lend plan and yield plan commands to
  include moonwell
- Add len(dec) guard in planner execMulticall3 to prevent potential
  panic on empty aggregate3 response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moonwell uses Compound v2 contracts where all calls operate on
msg.sender only. Passing --on-behalf-of was silently ignored.
Now returns CodeUnsupported error early, matching the existing
alternate-recipient rejection in the planner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflicts: CHANGELOG.md (interleave Moonwell + analytics entries),
runner.go (keep both --rpc-url flag and MarkFlagRequired calls).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update lending, yield, quickstart, providers-and-auth, and
execution-design docs to include Moonwell as a supported provider
on Base and Optimism.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ggonzalez94 ggonzalez94 merged commit 47f55b7 into main Mar 20, 2026
16 checks passed
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

rawResults := dec[0].([]struct {
Success bool `json:"success"`
ReturnData []byte `json:"returnData"`
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unchecked type assertion can panic on malformed RPC

Medium Severity

plannerExecMulticall3 uses an unchecked type assertion (dec[0].([]struct{...})) that will panic if the RPC returns unexpected data. The equivalent function execMulticall3 in client.go uses the safe form (decoded[0].([]struct{...}) with , ok check and error return). A malformed or unexpected RPC response would crash the CLI instead of returning a user-friendly error.

Additional Locations (1)
Fix in Cursor Fix in Web

func MoonwellRewardDistributor(chainID int64) (string, bool) {
value, ok := moonwellRewardDistributorByChainID[chainID]
return value, ok
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Exported function MoonwellRewardDistributor is never called anywhere

Low Severity

MoonwellRewardDistributor and its backing map moonwellRewardDistributorByChainID are defined and exported but never referenced anywhere in the codebase. A grep for MoonwellRewardDistributor only returns the definition site. This is dead code that adds maintenance burden without providing any value.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

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

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: d33f4bc398

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/id/amount.go
Comment on lines +28 to +30
// "max" resolves to uint256.max — used by repay to close full borrow balance.
if strings.EqualFold(strings.TrimSpace(baseUnits), "max") {
return MaxUint256, "max", nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope max amount sentinel to repay-only flows

NormalizeAmount now converts --amount max to uint256.max unconditionally, but this helper is shared by many non-repay paths (swap/bridge quotes and lend/yield planners). As a result, commands that do not define a max semantic now accept it and proceed with an unrealistic base-unit amount instead of returning a usage error, which can produce unusable quotes or reverting plans (for example, withdraw paths become redeemUnderlying(2^256-1)). This is a behavior regression from strict numeric validation and should be gated to the specific repay flows that support it.

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.

2 participants