Add swap action via PuddleSwap (Monad testnet) - #54
Conversation
portdeveloper
left a comment
There was a problem hiding this comment.
Please rebase onto current main now that #50 has landed and define the spend-policy behavior for swaps. The current branch bypasses policy completely, so a native MON swap does not hit maxPerSend or maxPerSession, and session accounting never advances. A configured policy must not silently allow a write it cannot evaluate; enforce the applicable rules or refuse the swap with a clear reason, with focused tests.
The core transaction builder is not covered by the new unit suite. test/swap.test.mjs checks parsing and route helpers, but it never decodes buildSwapCalls(). Add calldata tests for the native-input, native-output, and token-to-token branches, including the approval call, and verify the router target, call value, path, recipient, deadline, and min-out.
The advertised dry-run does not currently prove those calls execute. sendCalls() catches every quoteSendTransaction() failure and still returns a successful DRY RUN, so bad calldata, a router revert, and an unavailable estimator are indistinguishable. Fail closed when an available simulation rejects the calls, or provide a confirmed testnet broadcast that exercises the built UserOp before merge.
One smaller confirmation bug also contradicts the documented behavior: Number.parseInt() accepts input such as 1junk as route 1 even though anything except y or a listed number should cancel. Require the entire trimmed answer to be a valid route number and test it.
afc1c00 to
e24d722
Compare
|
Rebased onto current Spend policy. Swaps no longer bypass it. Native MON in hits
Dry-run.
Confirm parser. Whole-string match only (
|
|
Follow-up on the same PR ( Auto-best + Re-quote at confirm, then freeze. At Multicall3. Quotes go through Defaults. Slippage 1% (PuddleSwap). Deadline still 10 minutes, clock starts at confirm — preview now says Policy reuse (no new No new broadcast. The native-gas UserOp from the last comment still stands: https://testnet.monadscan.com/tx/0x13e3caadca8cc2d0e394e85a7c1b5bde84339dd120f4ce31b69a9c84f938a609 Swap + policy unit tests pass. Windows |
portdeveloper
left a comment
There was a problem hiding this comment.
The substantive swap blockers from the previous review are resolved: spend policy is enforced, calldata is decoded in tests, route selection is strict, the quote is refreshed without weakening the displayed min-out, and the linked successful UserOp contains the expected PuddleSwap swap call.
Before approval, please rebase onto current main now that #55 has merged and resolve the src/tools.mjs conflict while preserving #55’s send-validation and safeEcho behavior. Re-run CI after the rebase; I’ll do a focused re-review of the resolution.
0888f64 to
1dd1589
Compare
|
Rebased onto current
Swap behavior is unchanged: spend policy, auto-best + Please re-review the resolution. Same PR, no new one. |
|
#49 has landed and this branch now conflicts with current main. The reviewed swap work remains approved. Please rebase, preserve both features in the conflict resolution, and rerun CI; I will do a focused resolution check. |
1dd1589 to
2d622df
Compare
|
Rebased onto current Kept from #49: Kept from this PR: PuddleSwap swap, spend policy, auto-best + Swap + policy + send-args + accounts unit tests pass locally. Same PR, no new one. |
|
can you fix the conflicts |
2d622df to
3f60bc6
Compare
|
Rebased onto current Kept all three:
Swap, policy, send-args, NFT, and related unit tests pass locally. CI should go green. |
What this changes
Adds a testnet
swapaction so the agent can trade tokens on Monad, not only send them.Router: PuddleSwap — portdeveloper's Uniswap V2 DEX on Monad testnet (
chainId 10143). Quotes are batchedeth_calls to the router (getAmountsOutvia Multicall3). No swap API and no extra key.I looked at Monorail Pathfinder first (mentioned in the claim). The testnet Pathfinder endpoint was not usable from here, so this PR pins one RPC-only DEX instead of shipping a dead aggregator client. PancakeSwap / Uniswap V3 / other venues are a follow-up: they are not Uniswap V2
getAmountsOutrouters.Closes #14
Behaviour
{"action":"swap","amountIn":"0.1","tokenIn":"MON","tokenOut":"USDC"}/swap 0.1 MON USDC(skips the model)"swap 0.1 MON for USDC"via JSON from QVAC, with a phrase fallback for small modelsgetAmountsOutwins; confirm isy/Nlike sendy, one silent re-quote (Uniswap/CowSwap "fresh quote before sign"). If output is still ≥ the min-out that was shown, that path is locked. If it fell below, refuse (cancel and/swapagain). If RPC fails, keep the snapshot. No 6s ticker, no second promptMON ↔ WMONis a wrap, not a swap, and is refusedSWAP_SLIPPAGE_PERCENT). Mainnet hasdex: nulland refusesmaxPerSend/maxPerSessionand charges the session. ERC-20-in + those MON limits → refuse (cannot be evaluated). Allowlist does not applyPinned testnet addresses (from portdeveloper/puddleswap
config/addresses/10143.json):0x430c23895c8D44883526e3E0B09327dAD87666600x97B3070F9Da6C002343862b35E68Bd8e226089430x534b2f3A21130d7a60830c2Df862319e593943A30x1314b22df27BDcD4F8D11a0f4185943e55748917Official catalog WMON
0xFb8b…c541is still used for balance reads. Swap routing uses Puddle's wrap, because that is where the pools live.How I tested it
npm run buildsucceedstest/swap.test.mjs,test/policy.test.mjs)npm teston Windows: pre-existingtest/fetch-model.test.mjsfailures (C:\tmpmissing). CI is Ubuntu; that test is unrelated to swapnpm run smoke: swap section passed (live quote + dry-run UserOp). FullSMOKE_OKcan still fail on the existing NLsendpath when SmolLM2 truncates an address — not introduced by this PRModel / platform: Windows, Node 22, local SmolLM2 GGUF,
MONAD_NETWORK=testnet.Live quote (0.1 MON → USDC)
Best path is still
MON → USDT → USDC(~2.11 USDC) vs directMON → USDC(~0.41 USDC). Thin testnet pools, not agent arbitrage. Confirm shows that one winning route.parseAction("swap 0.1 MON for USDC")→{"action":"swap","amountIn":"0.1","tokenIn":"MON","tokenOut":"USDC"}Scope check
swap+/swap). Mainnet still refuses. No bridges, no mainnet DEX, no second aggregator in this PR.Issue #14 pointed at
src/tools.mjsandsrc/wallet.mjs. Those two are where the action is registered and the UserOp is sent. The extra files are the minimum to keep DEX math out of the wallet layer, wire/swap, pin the testnet router, and stop existing tests from treatingswapas unknown.Conventions
npm(not pnpm/yarn) and did not add a globalsodium-nativeoverride..env, seeds, keys, or model weights.