feat(protocols): add Merkl rewards claim adapter for Monad mainnet - #187
Conversation
|
Hi @pillowtalk-Qy @nishuzumi — this is the refreshed replacement for accidentally closed #173. I recovered exact audited head 985ca86, updated it against current main, addressed the lockfile blocker from the first audit, and reran the current verification suite. The PR body lists the exact differences from the previously audited head. @pillowtalk-Qy, a range-diff/final review against this exact head would be very welcome. |
|
Hi @portdeveloper — could #187 be registered as my current MOST claim? This is the refreshed replacement for #173, which was accidentally closed when I deleted the source fork. The Merkl implementation was already independently reviewed on the old exact head and #187 brings it forward onto current main. I noticed MOST claims are normally registered from scoped issues, while this contribution originated directly as a PR rather than from a separate Merkl issue. If a linked scoped issue is required for the claim, please let me know the preferred way to handle it. |
nishuzumi
left a comment
There was a problem hiding this comment.
I audited exact head e0877da53b44c6a6db8b9f9d04201c2ec195b5cf against current main@e5ef4f310a2d879e26b73edb207f662f231ba948.
This PR gives Agents first-class Merkl reward discovery and safe fixed-Distributor self-claim construction, and adds the package to the default MCP composition. The claim boundary, off-chain-candidate/on-chain-verification model, cumulative-versus-incremental accounting, exhaustive Receipt, deployment provenance, and current-main lock refresh are coherent. Two changes remain required:
-
[medium] Publish an accurate RiskLabel.
packages/protocols/merkl/src/adapter.ts:121-124declaresrisk: ["fundOut"]while explicitly acknowledging that this claim is inflow-only.CONTEXT.mddefinesfundOutas assets leaving the account in the current transaction.Registry.loadtherefore exposes a known-false public safety fact to Agents. Existing aPriori/FastLane placeholders are documented debt, not permission to add another contradictory contract. Please resolve #164 with the maintainer-approved Core representation, then use that representation here; do not shipfundOutfor this self-claim. -
[low] Complete the required compile-time Protocol fixture.
packages/protocols/merkl/test/types.fixture.tscovers parameter and result inference, but not this package's ABI-genericHandlecontract or invalid Receipt-name binding.CONTRIBUTING.md:48, ADR 0001, and the package template require positive and@ts-expect-errornegative coverage for valid/invalid Handle calls and Receipt-name autocomplete. Please add validclaim/read Handle calls, rejected unknown/bad-argument calls, and an invalid Receipt binding fixture.
Independent verification on this exact tree passed frozen install, lint, build, typecheck, the full clean-environment offline suite, the focused MCP suite, the focused Merkl live suite (30/30, including a zero-Warning self-claim with exact Claimed -> Transfer evidence), the keyed MonadScan semantic ABI suite (3/3), and pnpm audit --prod --audit-level high with no known vulnerabilities. I also independently matched the verified Distributor source, active ERC-1967 implementation, current on-chain root, API candidate, and Merkle proof. GitHub-hosted checks are not currently reported for this PR.
|
Thanks for the detailed review. I’ve addressed the compile-time fixture finding in The Merkl fixture now covers the ABI-typed Distributor Handle with valid Fresh local checks pass: frozen install, lint, build, typecheck, Merkl 30/30, offline suite, and The remaining blocker is the RiskLabel point. I’ve intentionally left that untouched rather than choosing a Core representation inside this PR. Could you confirm the intended direction for #164 — |
|
#164 is decided: |
|
Core #190 merged on September 9, so the dependency for this revision is available. Please rebase onto current main and apply the change nishuzumi requested: set claim to |
e0aa707 to
5b119d4
Compare
|
@nishuzumi Core #190 is now included in current main. I rebased #187 onto e958f7f, switched Merkl claim metadata to the explicitly authored risk: [], removed the placeholder, and added the Receipt assertion that no Change leaves the acting account. Fresh results: Merkl 30/30, offline 717 passed with 45 expected live skips, MCP 23/23, and typecheck/build/lint pass; full pnpm test is blocked only by five unrelated Aave live-fixture failures. Please re-review the updated head 5b119d4. |
|
@nishuzumi there are fresh revisions for your re-review: #187 now uses #198 is past the review SLA and still needs the keyed aprMON ABI check. I read its source-table and generation changes, but the shared Aave failure still prevents a complete hosted live-suite result. Please finish its review and the pending Merkl/Kuru re-reviews once the validation is available. |
…and show it can be refuted
nishuzumi
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 5b119d4e against main@f6df1b0, plus my follow-up cd040b2.
Both items from the 2026-09-01 review are resolved. claim now declares risk: [] with the placeholder gone; the type fixture covers the ABI-typed Distributor Handle (valid claim/read calls, rejected unknown/bad-argument calls) and both valid and invalid Receipt-name bindings — all sixteen @ts-expect-error directives are load-bearing. The package delta since the audited head is exactly the four requested hunks; the non-package hunks are byte-identical across the rebase; the lockfile adds only the merkl importer.
On the risk: [] contract: I probed seven outbound shapes (ERC-20 or native Transfer from the acting account in every position) and claimReceipt fails closed on all of them — strict Claimed/Transfer pairing, Distributor-only sender, user-only recipient, amount equality — so no Merkl Receipt can carry an outbound Change. The test-level receiptMovesAssetsOut check is therefore documentary, and cd040b2 makes it honest: it is asserted on the multi-token Receipt too, and one committed case shows the parser rejecting a Transfer from the account and the helper flipping to true, so the declaration is demonstrably refutable rather than asserted only on the happy path.
Verification on this head: frozen install, lint, build, typecheck, full offline suite (717 passed / 45 skipped), Merkl live 30/30 including the zero-Warning mainnet self-claim, keyed MonadScan ABI suite 3/3, MCP 23/23. pnpm audit --prod is red on the PR head only because its base predates #196; on the merged tree it reports no advisories. Remaining live failures (Aave #201, Kuru #194) are baseline.
|
@chin0312 merged as |
What and why
Add a first-class Merkl rewards Protocol package for Monad mainnet. Agents can discover an account's Merkl rewards with an on-chain cross-check, select 1-16 reward tokens, build one unsigned self-claim transaction, simulate it, and receive an exhaustive typed Receipt proving the actual payout.
Replacement / prior review provenance
This is the canonical replacement for #173. That PR closed when its source fork was accidentally deleted; the contribution was not intentionally withdrawn and was not rejected.
GitHub's preserved pull ref was recovered and verified at exact old head
985ca861220bf572eb319b6900230bdfb8156eea.pillowtalk-Qycompleted the first independent audit of that exact head and found no new claim/Receipt evidence blocker. The two original commits were replayed with their original attribution, then refreshed against current main.Replacement for #173.
Current-main refresh
This branch is based on
e958f7f40c12c3ba514d93a18b02bed7eb8cdca0, the latestupstream/mainfetched before this refresh.Differences from the previously audited head:
8.5.16tsup peer snapshot to current main's existing PostCSS8.5.23snapshot;risk: [], removed the temporary placeholder, and added a Receipt assertion that no Change moves assets out of the account;Merkl claim/query/Receipt execution semantics remain unchanged from audited head
985ca86. This refresh changes the Registry metadata to the Core-approvedrisk: [], removes the old compatibility placeholder, and adds a no-outbound-asset Receipt assertion; the Distributor proxy, implementation, ABI, bytecode hashes, safety boundaries, Query, Capability, and Receipt parser remain unchanged and were re-verified live.Adapter surface
merkl.rewards({ account })merkl.claim({ tokens }){ operation: "claim", account, rewards: [{ token, amount }] }The Capability accepts only an ordered list of 1-16 unique reward-token addresses. It owns exactly one direct unsigned
Distributor.claimtransaction and requires no approval.Safety boundaries
ActionCtx.account; the Agent cannot inject a user, Distributor, recipient, cumulative amount, proof, or calldata.reloadChainId=143for construction), while the active root, claimed amount, and effective recipient are read from the Distributor.keccak256(abi.encode(user, token, cumulativeAmount))with sorted proof pairs. A valid single-leaf empty proof is accepted only when the leaf equals the active root.claim; the positive incremental payout iscumulative - onchain claimed. Pending rewards never enter calldata, and cumulative values are bounded touint208.Distributor.Claimed(user, token, incrementalAmount)thentoken.Transfer(Distributor, user, incrementalAmount). Emitters, parties, token, amount, identity, length, and order are authenticated; malformed, duplicate, missing, reordered, decoy, ambiguous, and unexplained Changes fail closed.Excluded scope remains claims for other users, operator controls, recipient configuration,
claimWithRecipient, callbacks, swaps, vault deposits, campaign/admin/dispute/tree/governance operations, cross-chain claims, and arbitrary Distributor addresses.ABI/deployment provenance
0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae0x3f0fa7847b1b2e4515a93e05b29f115d9bb51d850x1663c7ebc964dfa69a98528bccf3431438c5149fbabb8313d33374df61f3985a0x9c2edb9dffd093d12e7361203d857bf6d1bd208fcc1aeff7a8b6ac73323a04e7The live keyless deployment suite re-read the EIP-1967 implementation slot, matched both bytecode hashes, checked required selectors and the
Claimedtopic, and read a non-zero active root. The committed 73-entry ABI remains deterministically rendered from the explorer-verified implementation and protected against hand edits.Current RiskLabel treatment
The claim is inflow-only. Core #190 now allows an explicitly authored empty risk list while keeping the field required, so Merkl uses
risk: []because no current closed-set label accurately describes this inflow-only operation. The Receipt test retains relevant Changes and asserts that no asset leaves the acting account, as required by #164. No Core changes are included in this PR.Current-main integration
@themoss/protocol-merklto the default MCP Registry composition;merkl.rewardsdiscovery/load andmerkl.claimdiscovery/load;@themoss/protocol-merkland@themoss/mcp-server.Verification
Fresh local results on this exact branch:
pnpm install --frozen-lockfile— passed with pnpm 11.10.0pnpm lint— 287 files checkedpnpm build— 21/22 workspace projects built (the private root is not a build project)pnpm typecheck— 21/22 workspace projects passedpnpm test:offline— 717 passed, 45 expected live skipspnpm test— blocked by 5 unrelated Aave Monad live-fixture failures (all Merkl tests and offline tests pass); see the local verification note belowpnpm --filter @themoss/protocol-merkl test— 30/30 passedpnpm --filter @themoss/mcp-server test— 23/23 passedpnpm audit --prod --audit-level high— exited 0 with 0 high/critical vulnerabilities; 3 moderate Hono advisories are inherited through the MCP SDKpnpm changeset status --since=upstream/main— Merkl and MCP server minor bumpspnpm testreached unrelated Aave mainnet fixture failures (expired address-book quarantine plus no live account for the Aave health/supply/borrow/repay roles); this Merkl-only change did not alter Aave sources.pnpm audit --prod --audit-level highreported three moderate Hono advisories (<4.13.5) through the MCP SDK; no high or critical advisories were reported, and no dependency changed in this Merkl refresh.git diff --checkLive simulation
The unsigned Monad mainnet happy path passed for public account
0x461549c73FFfB676860A0E49F5DaABEcf4E8D2d7and reward token0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A, with incremental claimable amount49640922692037687136. It produced one transaction, no halt, zero Warnings, and the exhaustive ordered two-ChangeClaimed -> TransferReceipt. No signing, broadcasting, private key, or storage mutation occurred.Hosted CI
GitHub created CI run 34684359452 for this refreshed head, but it is
action_requiredand no hosted job executed pending maintainer approval. Hosted CI is therefore not claimed as passing.ABI-online caveat
MONADSCAN_API_KEYwas unavailable, so the keyed online ABI comparison was not run. All keyless ABI derivation, proxy/implementation, bytecode, selector, topic, read-surface, and live simulation checks passed.AI assistance
OpenAI Codex assisted with recovery, current-main adaptation, and verification. The results above were run against the resulting committed tree.