From 0a4f50c1f8c71b9cc4cf9fb15cd46b4c4181a4c1 Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Tue, 14 Apr 2026 22:09:10 -0400 Subject: [PATCH 1/8] feat(cli): add --chains comma list for balances - Parallel Sequence indexer fan-out per chain (max 20) - JSON shape: multiChain + chains[] when 2+ chains; flat unchanged otherwise - Document in README and agent skills (incl. connector-ui copies) --- packages/connector-ui/public/SKILL.md | 3 +- packages/connector-ui/public/skills/SKILL.md | 3 +- packages/polygon-agent-cli/README.md | 5 +- packages/polygon-agent-cli/skills/SKILL.md | 3 +- .../src/commands/operations.ts | 225 ++++++++++++++---- skills/SKILL.md | 3 +- 6 files changed, 191 insertions(+), 51 deletions(-) diff --git a/packages/connector-ui/public/SKILL.md b/packages/connector-ui/public/SKILL.md index 91625da..c65b57e 100644 --- a/packages/connector-ui/public/SKILL.md +++ b/packages/connector-ui/public/SKILL.md @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name ] ### Operations ```bash -polygon-agent balances [--wallet ] [--chain ] +polygon-agent balances [--wallet ] [--chain ] [--chains ] polygon-agent send --to --amount [--symbol ] [--token ] [--decimals ] [--broadcast] polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Dry-run by default** — all write commands require `--broadcast` to execute - **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create` +- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each) - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` diff --git a/packages/connector-ui/public/skills/SKILL.md b/packages/connector-ui/public/skills/SKILL.md index 91625da..c65b57e 100644 --- a/packages/connector-ui/public/skills/SKILL.md +++ b/packages/connector-ui/public/skills/SKILL.md @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name ] ### Operations ```bash -polygon-agent balances [--wallet ] [--chain ] +polygon-agent balances [--wallet ] [--chain ] [--chains ] polygon-agent send --to --amount [--symbol ] [--token ] [--decimals ] [--broadcast] polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Dry-run by default** — all write commands require `--broadcast` to execute - **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create` +- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each) - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` diff --git a/packages/polygon-agent-cli/README.md b/packages/polygon-agent-cli/README.md index 3c84957..ceae61b 100644 --- a/packages/polygon-agent-cli/README.md +++ b/packages/polygon-agent-cli/README.md @@ -173,7 +173,9 @@ polygon-agent fund # Open funding widget ### Token Operations ```bash -polygon-agent balances # Check all balances +polygon-agent balances # Balances on session default chain +polygon-agent balances --chain arbitrum # Single chain override +polygon-agent balances --chains polygon,base,arbitrum # Same wallet, multiple chains (JSON) polygon-agent send --to 0x... --amount 1.0 # Send POL (dry-run) polygon-agent send --symbol USDC --to 0x... --amount 10 --broadcast polygon-agent swap --from USDC --to USDT --amount 5 --broadcast @@ -194,6 +196,7 @@ polygon-agent agent reviews --agent-id | ------------- | ---------------------- | -------------------- | | Wallet name | `main` | `--name ` | | Chain | `polygon` | `--chain ` | +| Multi-chain balances | — | `--chains ` (comma-separated, max 20; overrides `--chain`) | | Wallet create | Auto-wait for approval | `--no-wait` | | Broadcast | Dry-run (preview) | `--broadcast` | diff --git a/packages/polygon-agent-cli/skills/SKILL.md b/packages/polygon-agent-cli/skills/SKILL.md index 91625da..c65b57e 100644 --- a/packages/polygon-agent-cli/skills/SKILL.md +++ b/packages/polygon-agent-cli/skills/SKILL.md @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name ] ### Operations ```bash -polygon-agent balances [--wallet ] [--chain ] +polygon-agent balances [--wallet ] [--chain ] [--chains ] polygon-agent send --to --amount [--symbol ] [--token ] [--decimals ] [--broadcast] polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Dry-run by default** — all write commands require `--broadcast` to execute - **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create` +- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each) - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` diff --git a/packages/polygon-agent-cli/src/commands/operations.ts b/packages/polygon-agent-cli/src/commands/operations.ts index 7e84c07..df66c17 100644 --- a/packages/polygon-agent-cli/src/commands/operations.ts +++ b/packages/polygon-agent-cli/src/commands/operations.ts @@ -111,16 +111,114 @@ async function getTokenConfig({ const bigintReplacer = (_k: string, v: unknown) => (typeof v === 'bigint' ? v.toString() : v); +const BALANCES_MAX_CHAINS = 20; + +function parseCommaChainList(chainsArg: string | undefined): string[] { + if (!chainsArg || typeof chainsArg !== 'string') return []; + return chainsArg + .split(',') + .map((s) => s.trim()) + .filter(Boolean); +} + +type BalanceRowJson = + | { type: 'native'; symbol: string; balance: string } + | { + type: 'erc20'; + symbol: string; + name?: string; + contractAddress: string; + balance: string; + }; + +async function fetchBalancesRowsForChain( + walletAddress: string, + chainSpec: string, + indexerKey: string +): Promise<{ chainId: number; chain: string; balances: BalanceRowJson[] }> { + const network = resolveNetwork(chainSpec); + const nativeDecimals = network.nativeToken?.decimals ?? 18; + const nativeSymbol = network.nativeToken?.symbol || 'POL'; + + const { SequenceIndexer } = await import('@0xsequence/indexer'); + const indexerUrl = getChainIndexerUrl(network.chainId); + const indexer = new SequenceIndexer(indexerUrl, indexerKey); + + const [nativeRes, tokenRes] = await Promise.all([ + indexer.getNativeTokenBalance({ + accountAddress: walletAddress + }), + indexer.getTokenBalances({ + accountAddress: walletAddress, + includeMetadata: true + }) + ]); + + const nativeWei = nativeRes?.balance?.balance || '0'; + const native: BalanceRowJson[] = [ + { + type: 'native', + symbol: nativeSymbol, + balance: formatUnits(BigInt(nativeWei), nativeDecimals) + } + ]; + + const erc20: BalanceRowJson[] = (tokenRes?.balances || []).map( + (b: { + contractInfo?: { symbol?: string; name?: string; decimals?: number }; + contractAddress: string; + balance?: string; + }) => ({ + type: 'erc20' as const, + symbol: b.contractInfo?.symbol || 'ERC20', + name: b.contractInfo?.name || undefined, + contractAddress: b.contractAddress, + balance: formatUnits(b.balance || '0', b.contractInfo?.decimals ?? 18) + }) + ); + + return { + chainId: network.chainId, + chain: network.name, + balances: [...native, ...erc20] + }; +} + // --- balances --- export const balancesCommand: CommandModule = { command: 'balances', describe: 'Check token balances', - builder: (yargs) => withWalletAndChain(yargs), + builder: (yargs) => + withWalletAndChain(yargs).option('chains', { + type: 'string', + describe: + 'Comma-separated chain names or IDs (e.g. polygon,base,arbitrum). When set, overrides --chain. Two or more chains return multi-chain JSON (TTY included).' + }), handler: async (argv) => { const walletName = argv.wallet as string; + const chainListRaw = parseCommaChainList(argv.chains as string | undefined); + if (chainListRaw.length > BALANCES_MAX_CHAINS) { + console.error( + JSON.stringify( + { + ok: false, + error: `Too many chains in --chains (max ${BALANCES_MAX_CHAINS}).` + }, + null, + 2 + ) + ); + process.exit(1); + } + const chainList = chainListRaw; - if (!isTTY()) { - // Non-TTY: original JSON output + const preferChainsArg = chainList.length > 0; + const singleChainSpec = preferChainsArg + ? chainList[0] + : ((argv.chain as string) || undefined); + const multiChainMode = preferChainsArg && chainList.length > 1; + + if (multiChainMode || (preferChainsArg && !isTTY())) { try { const session = await loadWalletSession(walletName); if (!session) { @@ -135,46 +233,81 @@ export const balancesCommand: CommandModule = { throw new Error('Missing project access key (not in wallet session or environment)'); } - const network = resolveNetwork((argv.chain as string) || session.chain || 'polygon'); - const nativeDecimals = network.nativeToken?.decimals ?? 18; - const nativeSymbol = network.nativeToken?.symbol || 'POL'; - - const { SequenceIndexer } = await import('@0xsequence/indexer'); - const indexerUrl = getChainIndexerUrl(network.chainId); - const indexer = new SequenceIndexer(indexerUrl, indexerKey); - - const [nativeRes, tokenRes] = await Promise.all([ - indexer.getNativeTokenBalance({ - accountAddress: session.walletAddress - }), - indexer.getTokenBalances({ - accountAddress: session.walletAddress, - includeMetadata: true - }) - ]); + if (multiChainMode) { + const chainsOut = await Promise.all( + chainList.map((spec) => + fetchBalancesRowsForChain(session.walletAddress, spec, indexerKey) + ) + ); + console.log( + JSON.stringify( + { + ok: true, + walletName, + walletAddress: session.walletAddress, + multiChain: true, + chains: chainsOut + }, + bigintReplacer, + 2 + ) + ); + } else { + const one = await fetchBalancesRowsForChain( + session.walletAddress, + singleChainSpec!, + indexerKey + ); + console.log( + JSON.stringify( + { + ok: true, + walletName, + walletAddress: session.walletAddress, + chainId: one.chainId, + chain: one.chain, + balances: one.balances + }, + bigintReplacer, + 2 + ) + ); + } + } catch (error) { + console.error( + JSON.stringify( + { + ok: false, + error: (error as Error).message, + stack: (error as Error).stack + }, + null, + 2 + ) + ); + process.exit(1); + } + return; + } - const nativeWei = nativeRes?.balance?.balance || '0'; - const native = [ - { - type: 'native', - symbol: nativeSymbol, - balance: formatUnits(BigInt(nativeWei), nativeDecimals) - } - ]; + if (!isTTY()) { + // Non-TTY: original JSON output (single default / --chain) + try { + const session = await loadWalletSession(walletName); + if (!session) { + throw new Error(`Wallet not found: ${walletName}`); + } - const erc20 = (tokenRes?.balances || []).map( - (b: { - contractInfo?: { symbol?: string; name?: string; decimals?: number }; - contractAddress: string; - balance?: string; - }) => ({ - type: 'erc20', - symbol: b.contractInfo?.symbol || 'ERC20', - name: b.contractInfo?.name || undefined, - contractAddress: b.contractAddress, - balance: formatUnits(b.balance || '0', b.contractInfo?.decimals ?? 18) - }) - ); + const indexerKey = + process.env.SEQUENCE_INDEXER_ACCESS_KEY || + session.projectAccessKey || + process.env.SEQUENCE_PROJECT_ACCESS_KEY; + if (!indexerKey) { + throw new Error('Missing project access key (not in wallet session or environment)'); + } + + const chainSpec = (argv.chain as string) || session.chain || 'polygon'; + const one = await fetchBalancesRowsForChain(session.walletAddress, chainSpec, indexerKey); console.log( JSON.stringify( @@ -182,11 +315,11 @@ export const balancesCommand: CommandModule = { ok: true, walletName, walletAddress: session.walletAddress, - chainId: network.chainId, - chain: network.name, - balances: [...native, ...erc20] + chainId: one.chainId, + chain: one.chain, + balances: one.balances }, - null, + bigintReplacer, 2 ) ); @@ -211,7 +344,7 @@ export const balancesCommand: CommandModule = { await inkRender( React.createElement(BalancesUI, { walletName, - chainOverride: argv.chain as string | undefined + chainOverride: preferChainsArg ? singleChainSpec : (argv.chain as string | undefined) }) ); } catch { diff --git a/skills/SKILL.md b/skills/SKILL.md index 91625da..c65b57e 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name ] ### Operations ```bash -polygon-agent balances [--wallet ] [--chain ] +polygon-agent balances [--wallet ] [--chain ] [--chains ] polygon-agent send --to --amount [--symbol ] [--token ] [--decimals ] [--broadcast] polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Dry-run by default** — all write commands require `--broadcast` to execute - **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create` +- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each) - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` From 494ccbc3e65c0a2cf87ed22794431cfcc47e183d Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Tue, 14 Apr 2026 22:53:00 -0400 Subject: [PATCH 2/8] feat(cli): add withdraw for Aave aTokens and ERC-4626 vaults - withdraw: POOL/UNDERLYING + Pool.withdraw; ERC-4626 redeem (max or convertToShares) - docs: README, skills, polygon-defi; CHANGELOG Unreleased - fix: strip trailing slash in getExplorerUrl --- packages/connector-ui/public/SKILL.md | 9 +- packages/connector-ui/public/skills/SKILL.md | 9 +- .../public/skills/polygon-defi/SKILL.md | 19 + packages/polygon-agent-cli/CHANGELOG.md | 10 + packages/polygon-agent-cli/README.md | 8 +- packages/polygon-agent-cli/skills/SKILL.md | 9 +- .../skills/polygon-defi/SKILL.md | 19 + .../src/commands/operations.ts | 380 ++++++++++++++++++ packages/polygon-agent-cli/src/index.ts | 2 + packages/polygon-agent-cli/src/lib/utils.ts | 3 +- skills/SKILL.md | 9 +- skills/polygon-defi/SKILL.md | 19 + 12 files changed, 486 insertions(+), 10 deletions(-) diff --git a/packages/connector-ui/public/SKILL.md b/packages/connector-ui/public/SKILL.md index c65b57e..1d373b2 100644 --- a/packages/connector-ui/public/SKILL.md +++ b/packages/connector-ui/public/SKILL.md @@ -1,6 +1,6 @@ --- name: Polygon Agent -description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit to earn yield, register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." +description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit or withdraw from yield (Aave aTokens, ERC-4626 vaults), register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." --- # Polygon Agentic CLI @@ -105,6 +105,7 @@ polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] polygon-agent swap --from --to --amount [--to-chain ] [--slippage ] [--broadcast] polygon-agent deposit --asset --amount [--protocol aave|morpho] [--broadcast] +polygon-agent withdraw --position --amount [--chain ] [--broadcast] polygon-agent fund [--wallet ] [--token ] polygon-agent x402-pay --url --wallet [--method GET] [--body ] [--header Key:Value] ``` @@ -131,6 +132,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` +- **`withdraw`** — `--position` = aToken or ERC-4626 vault; `--amount` = `max` or underlying units (Aave / vault). Dry-run JSON includes `poolAddress` / `vault`. Broadcast needs session on the **same chain** as `--chain`, with pool/vault + underlying token whitelisted where the relayer touches them - **`x402-pay`** — probes endpoint for 402, smart wallet funds builder EOA with exact token amount, EOA signs EIP-3009 payment. Chain auto-detected from 402 response - **`send-native --direct`** — bypasses ValueForwarder contract for direct EOA transfer - **Session permissions** — without `--usdc-limit` etc., session gets bare-bones defaults and may not transact @@ -163,6 +165,7 @@ CLI commands output JSON (non-TTY). After running a command, always render the r | `send` / `send-token` / `send-native` | One-liner summary: amount, symbol, recipient. If broadcast, show tx hash as a code span and explorer URL as a link. | | `swap` | Summary: `X FROM → Y TO` with chain. If broadcast, show deposit tx hash + explorer link. | | `deposit` | Summary: amount, asset, protocol, pool address. If broadcast, show tx hash + explorer link. | +| `withdraw` | Summary: `kind` (aave / erc4626), position, amount, pool or vault. If broadcast, show tx hash + explorer link. | | `fund` | Show the `fundingUrl` as a clickable link with a brief instruction to open it. | | `wallet create` / `wallet list` | Wallet name, truncated address, chain in a small table or bullet list. | | `agent register` | Show agent name and tx hash as a code span with Polygonscan link. Remind user to retrieve `agentId` from the Registered event on the Logs tab. | @@ -183,7 +186,7 @@ For specific workflows, fetch and load the relevant sub-skill: | Use Case | Skill URL | |----------|-----------| | Polymarket prediction market trading | https://agentconnect.polygon.technology/polygon-polymarket/SKILL.md | -| DeFi — swap, deposit, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | +| DeFi — swap, deposit, withdraw, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | | x402 discovery & pay-per-call APIs | https://agentconnect.polygon.technology/polygon-discovery/SKILL.md | --- @@ -201,6 +204,8 @@ For specific workflows, fetch and load the relevant sub-skill: | `Invalid code: hash mismatch` | Wrong 6-digit code entered — retry (3 attempts allowed) | | `Relay request not found` | Session expired or already used — re-run `wallet create` (or `wallet create --print-url`) | | Deposit session rejected | Re-create wallet with `--contract ` | +| `withdraw` / broadcast: wrong chain or session rejects | Use `wallet create --chain ` and `--contract` for pool/vault + underlying ERC-20 on that chain; omit tight `--usdc-limit` if it blocks fee transfers | +| `Stored explicit session is missing pk` | Re-link: `wallet import --code …` after `wallet create` | | Wrong recipient in Trails widget | Run `polygon-agent fund` (do not construct the URL manually) | | `x402-pay`: no 402 response | Endpoint doesn't require x402 payment, or URL is wrong | | `x402-pay`: payment token mismatch | Chain/token in the 402 response differs from wallet — check `--wallet` points to the right chain | diff --git a/packages/connector-ui/public/skills/SKILL.md b/packages/connector-ui/public/skills/SKILL.md index c65b57e..1d373b2 100644 --- a/packages/connector-ui/public/skills/SKILL.md +++ b/packages/connector-ui/public/skills/SKILL.md @@ -1,6 +1,6 @@ --- name: Polygon Agent -description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit to earn yield, register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." +description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit or withdraw from yield (Aave aTokens, ERC-4626 vaults), register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." --- # Polygon Agentic CLI @@ -105,6 +105,7 @@ polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] polygon-agent swap --from --to --amount [--to-chain ] [--slippage ] [--broadcast] polygon-agent deposit --asset --amount [--protocol aave|morpho] [--broadcast] +polygon-agent withdraw --position --amount [--chain ] [--broadcast] polygon-agent fund [--wallet ] [--token ] polygon-agent x402-pay --url --wallet [--method GET] [--body ] [--header Key:Value] ``` @@ -131,6 +132,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` +- **`withdraw`** — `--position` = aToken or ERC-4626 vault; `--amount` = `max` or underlying units (Aave / vault). Dry-run JSON includes `poolAddress` / `vault`. Broadcast needs session on the **same chain** as `--chain`, with pool/vault + underlying token whitelisted where the relayer touches them - **`x402-pay`** — probes endpoint for 402, smart wallet funds builder EOA with exact token amount, EOA signs EIP-3009 payment. Chain auto-detected from 402 response - **`send-native --direct`** — bypasses ValueForwarder contract for direct EOA transfer - **Session permissions** — without `--usdc-limit` etc., session gets bare-bones defaults and may not transact @@ -163,6 +165,7 @@ CLI commands output JSON (non-TTY). After running a command, always render the r | `send` / `send-token` / `send-native` | One-liner summary: amount, symbol, recipient. If broadcast, show tx hash as a code span and explorer URL as a link. | | `swap` | Summary: `X FROM → Y TO` with chain. If broadcast, show deposit tx hash + explorer link. | | `deposit` | Summary: amount, asset, protocol, pool address. If broadcast, show tx hash + explorer link. | +| `withdraw` | Summary: `kind` (aave / erc4626), position, amount, pool or vault. If broadcast, show tx hash + explorer link. | | `fund` | Show the `fundingUrl` as a clickable link with a brief instruction to open it. | | `wallet create` / `wallet list` | Wallet name, truncated address, chain in a small table or bullet list. | | `agent register` | Show agent name and tx hash as a code span with Polygonscan link. Remind user to retrieve `agentId` from the Registered event on the Logs tab. | @@ -183,7 +186,7 @@ For specific workflows, fetch and load the relevant sub-skill: | Use Case | Skill URL | |----------|-----------| | Polymarket prediction market trading | https://agentconnect.polygon.technology/polygon-polymarket/SKILL.md | -| DeFi — swap, deposit, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | +| DeFi — swap, deposit, withdraw, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | | x402 discovery & pay-per-call APIs | https://agentconnect.polygon.technology/polygon-discovery/SKILL.md | --- @@ -201,6 +204,8 @@ For specific workflows, fetch and load the relevant sub-skill: | `Invalid code: hash mismatch` | Wrong 6-digit code entered — retry (3 attempts allowed) | | `Relay request not found` | Session expired or already used — re-run `wallet create` (or `wallet create --print-url`) | | Deposit session rejected | Re-create wallet with `--contract ` | +| `withdraw` / broadcast: wrong chain or session rejects | Use `wallet create --chain ` and `--contract` for pool/vault + underlying ERC-20 on that chain; omit tight `--usdc-limit` if it blocks fee transfers | +| `Stored explicit session is missing pk` | Re-link: `wallet import --code …` after `wallet create` | | Wrong recipient in Trails widget | Run `polygon-agent fund` (do not construct the URL manually) | | `x402-pay`: no 402 response | Endpoint doesn't require x402 payment, or URL is wrong | | `x402-pay`: payment token mismatch | Chain/token in the 402 response differs from wallet — check `--wallet` points to the right chain | diff --git a/packages/connector-ui/public/skills/polygon-defi/SKILL.md b/packages/connector-ui/public/skills/polygon-defi/SKILL.md index 3751d99..0afcbff 100644 --- a/packages/connector-ui/public/skills/polygon-defi/SKILL.md +++ b/packages/connector-ui/public/skills/polygon-defi/SKILL.md @@ -129,6 +129,25 @@ polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast Vault/pool addresses are resolved dynamically from Trails — they are not hardcoded. The dry-run output includes `depositAddress` so you can inspect the exact contract before broadcasting. +## Withdraw (Aave aToken or ERC-4626 vault) + +Pass the **position token** you hold: an **Aave aToken** address, or a **Morpho / ERC-4626 vault** (share) address. The CLI resolves the Aave **Pool** via `POOL()` on the aToken, or uses `redeem` on the vault. Dry-run by default. + +```bash +# Full exit from an Aave position (aToken from balances output) +polygon-agent withdraw --position 0x68215b6533c47ff9f7125ac95adf00fe4a62f79e --amount max --chain mainnet + +# Partial Aave withdraw (underlying units, e.g. USDC) +polygon-agent withdraw --position --amount 0.5 --chain mainnet --broadcast + +# ERC-4626: max redeems all shares; partial amount is underlying units (convertToShares) +polygon-agent withdraw --position --amount max --chain polygon --broadcast +``` + +Whitelist the **pool** (Aave) or **vault** contract on the session if the wallet rejects the call (`polygon-agent wallet create --contract `). + +**Same chain as the transaction:** if you use `withdraw --chain mainnet`, create or refresh the session with **`wallet create --chain mainnet`** (not only Polygon defaults). Include **`--contract`** for the **pool** and for the **underlying ERC-20** on that chain (e.g. mainnet USDC `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) so fee / helper transfers are allowed. Tight **`--usdc-limit`** can block those — omit or relax for yield exits. + ### Session Whitelisting If the deposit is rejected with a session permission error, the pool's contract address needs to be whitelisted when creating the wallet session: diff --git a/packages/polygon-agent-cli/CHANGELOG.md b/packages/polygon-agent-cli/CHANGELOG.md index e392d43..091a24c 100644 --- a/packages/polygon-agent-cli/CHANGELOG.md +++ b/packages/polygon-agent-cli/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [Unreleased] + +### Features + +* **cli:** `withdraw` command — exit Aave v3 via aToken (`Pool.withdraw`) or ERC-4626 vaults (`redeem`); dry-run shows pool/vault and calldata ([docs](README.md#cli-reference)) + +### Bug Fixes + +* **cli:** avoid double slash in transaction explorer URLs when `blockExplorer.rootUrl` ends with `/` + ## [0.7.2](https://github.com/0xPolygon/polygon-agent-cli/compare/@polygonlabs/agent-cli@0.7.1...@polygonlabs/agent-cli@0.7.2) (2026-04-14) diff --git a/packages/polygon-agent-cli/README.md b/packages/polygon-agent-cli/README.md index ceae61b..283a297 100644 --- a/packages/polygon-agent-cli/README.md +++ b/packages/polygon-agent-cli/README.md @@ -124,7 +124,7 @@ Wallet sessions are created through a secure handshake between the CLI, the Conn | ------------ | -------------------------------------------------------------------------------------------- | ------------------------------- | | **Bridging** | Move assets cross-chain into your Polygon wallet and fund the initial flows to your wallet | `fund` | | **Swapping** | Token swaps with configurable slippage seamlessly built in | `swap` | -| **Actions** | Composable onchain operations (deposit into a DeFi vault, stake with your favorite protocol) | `send`, `deposit`, `send-token` | +| **Actions** | Composable onchain operations (deposit / withdraw from yield, send tokens) | `send`, `deposit`, `withdraw`, `send-token` | ### Onchain Agentic Identity @@ -179,8 +179,14 @@ polygon-agent balances --chains polygon,base,arbitrum # Same wallet, multiple c polygon-agent send --to 0x... --amount 1.0 # Send POL (dry-run) polygon-agent send --symbol USDC --to 0x... --amount 10 --broadcast polygon-agent swap --from USDC --to USDT --amount 5 --broadcast +polygon-agent withdraw --position --amount max [--chain ] # dry-run; add --broadcast +polygon-agent withdraw --position --amount 0.5 --chain mainnet --broadcast # partial (underlying units) ``` +**`withdraw`** exits **Aave v3** positions using your **aToken** address (`POOL()` + `UNDERLYING_ASSET_ADDRESS()` → `Pool.withdraw`), or **ERC-4626** vaults (e.g. Morpho) via `redeem`. Dry-run prints `poolAddress` / `vault` and calldata. + +For **`--broadcast`**, the session must allow the **pool or vault** and (on that chain) **fee / underlying ERC-20** touches — use `wallet create --chain --contract --contract `. If your default session is Polygon but you transact on **mainnet**, create or extend a **mainnet** session for that chain. + ### Agent Registry (ERC-8004) ```bash diff --git a/packages/polygon-agent-cli/skills/SKILL.md b/packages/polygon-agent-cli/skills/SKILL.md index c65b57e..1d373b2 100644 --- a/packages/polygon-agent-cli/skills/SKILL.md +++ b/packages/polygon-agent-cli/skills/SKILL.md @@ -1,6 +1,6 @@ --- name: Polygon Agent -description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit to earn yield, register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." +description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit or withdraw from yield (Aave aTokens, ERC-4626 vaults), register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." --- # Polygon Agentic CLI @@ -105,6 +105,7 @@ polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] polygon-agent swap --from --to --amount [--to-chain ] [--slippage ] [--broadcast] polygon-agent deposit --asset --amount [--protocol aave|morpho] [--broadcast] +polygon-agent withdraw --position --amount [--chain ] [--broadcast] polygon-agent fund [--wallet ] [--token ] polygon-agent x402-pay --url --wallet [--method GET] [--body ] [--header Key:Value] ``` @@ -131,6 +132,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` +- **`withdraw`** — `--position` = aToken or ERC-4626 vault; `--amount` = `max` or underlying units (Aave / vault). Dry-run JSON includes `poolAddress` / `vault`. Broadcast needs session on the **same chain** as `--chain`, with pool/vault + underlying token whitelisted where the relayer touches them - **`x402-pay`** — probes endpoint for 402, smart wallet funds builder EOA with exact token amount, EOA signs EIP-3009 payment. Chain auto-detected from 402 response - **`send-native --direct`** — bypasses ValueForwarder contract for direct EOA transfer - **Session permissions** — without `--usdc-limit` etc., session gets bare-bones defaults and may not transact @@ -163,6 +165,7 @@ CLI commands output JSON (non-TTY). After running a command, always render the r | `send` / `send-token` / `send-native` | One-liner summary: amount, symbol, recipient. If broadcast, show tx hash as a code span and explorer URL as a link. | | `swap` | Summary: `X FROM → Y TO` with chain. If broadcast, show deposit tx hash + explorer link. | | `deposit` | Summary: amount, asset, protocol, pool address. If broadcast, show tx hash + explorer link. | +| `withdraw` | Summary: `kind` (aave / erc4626), position, amount, pool or vault. If broadcast, show tx hash + explorer link. | | `fund` | Show the `fundingUrl` as a clickable link with a brief instruction to open it. | | `wallet create` / `wallet list` | Wallet name, truncated address, chain in a small table or bullet list. | | `agent register` | Show agent name and tx hash as a code span with Polygonscan link. Remind user to retrieve `agentId` from the Registered event on the Logs tab. | @@ -183,7 +186,7 @@ For specific workflows, fetch and load the relevant sub-skill: | Use Case | Skill URL | |----------|-----------| | Polymarket prediction market trading | https://agentconnect.polygon.technology/polygon-polymarket/SKILL.md | -| DeFi — swap, deposit, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | +| DeFi — swap, deposit, withdraw, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | | x402 discovery & pay-per-call APIs | https://agentconnect.polygon.technology/polygon-discovery/SKILL.md | --- @@ -201,6 +204,8 @@ For specific workflows, fetch and load the relevant sub-skill: | `Invalid code: hash mismatch` | Wrong 6-digit code entered — retry (3 attempts allowed) | | `Relay request not found` | Session expired or already used — re-run `wallet create` (or `wallet create --print-url`) | | Deposit session rejected | Re-create wallet with `--contract ` | +| `withdraw` / broadcast: wrong chain or session rejects | Use `wallet create --chain ` and `--contract` for pool/vault + underlying ERC-20 on that chain; omit tight `--usdc-limit` if it blocks fee transfers | +| `Stored explicit session is missing pk` | Re-link: `wallet import --code …` after `wallet create` | | Wrong recipient in Trails widget | Run `polygon-agent fund` (do not construct the URL manually) | | `x402-pay`: no 402 response | Endpoint doesn't require x402 payment, or URL is wrong | | `x402-pay`: payment token mismatch | Chain/token in the 402 response differs from wallet — check `--wallet` points to the right chain | diff --git a/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md b/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md index 3751d99..0afcbff 100644 --- a/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md +++ b/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md @@ -129,6 +129,25 @@ polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast Vault/pool addresses are resolved dynamically from Trails — they are not hardcoded. The dry-run output includes `depositAddress` so you can inspect the exact contract before broadcasting. +## Withdraw (Aave aToken or ERC-4626 vault) + +Pass the **position token** you hold: an **Aave aToken** address, or a **Morpho / ERC-4626 vault** (share) address. The CLI resolves the Aave **Pool** via `POOL()` on the aToken, or uses `redeem` on the vault. Dry-run by default. + +```bash +# Full exit from an Aave position (aToken from balances output) +polygon-agent withdraw --position 0x68215b6533c47ff9f7125ac95adf00fe4a62f79e --amount max --chain mainnet + +# Partial Aave withdraw (underlying units, e.g. USDC) +polygon-agent withdraw --position --amount 0.5 --chain mainnet --broadcast + +# ERC-4626: max redeems all shares; partial amount is underlying units (convertToShares) +polygon-agent withdraw --position --amount max --chain polygon --broadcast +``` + +Whitelist the **pool** (Aave) or **vault** contract on the session if the wallet rejects the call (`polygon-agent wallet create --contract `). + +**Same chain as the transaction:** if you use `withdraw --chain mainnet`, create or refresh the session with **`wallet create --chain mainnet`** (not only Polygon defaults). Include **`--contract`** for the **pool** and for the **underlying ERC-20** on that chain (e.g. mainnet USDC `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) so fee / helper transfers are allowed. Tight **`--usdc-limit`** can block those — omit or relax for yield exits. + ### Session Whitelisting If the deposit is rejected with a session permission error, the pool's contract address needs to be whitelisted when creating the wallet session: diff --git a/packages/polygon-agent-cli/src/commands/operations.ts b/packages/polygon-agent-cli/src/commands/operations.ts index df66c17..2ca16c4 100644 --- a/packages/polygon-agent-cli/src/commands/operations.ts +++ b/packages/polygon-agent-cli/src/commands/operations.ts @@ -10,6 +10,7 @@ import { formatUnits, parseUnits, getExplorerUrl, + getRpcUrl, fileCoerce } from '../lib/utils.ts'; import { isTTY, inkRender } from '../ui/render.js'; @@ -1322,6 +1323,385 @@ export const depositCommand: CommandModule = { } }; +const AAVE_ATOKEN_META_ABI = [ + { + name: 'POOL', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'address' }] + }, + { + name: 'UNDERLYING_ASSET_ADDRESS', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'address' }] + } +] as const; + +const ERC20_DECIMALS_ABI = [ + { + name: 'decimals', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint8' }] + } +] as const; + +const ERC20_BALANCE_OF_ABI = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }] + } +] as const; + +const ERC4626_ASSET_ABI = [ + { + name: 'asset', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'address' }] + } +] as const; + +const ERC4626_CONVERT_TO_SHARES_ABI = [ + { + name: 'convertToShares', + type: 'function', + stateMutability: 'view', + inputs: [{ name: 'assets', type: 'uint256' }], + outputs: [{ type: 'uint256' }] + } +] as const; + +const AAVE_POOL_WITHDRAW_ABI = [ + { + name: 'withdraw', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'asset', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'to', type: 'address' } + ], + outputs: [{ type: 'uint256' }] + } +] as const; + +const ERC4626_REDEEM_ABI = [ + { + name: 'redeem', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'shares', type: 'uint256' }, + { name: 'receiver', type: 'address' }, + { name: 'owner', type: 'address' } + ], + outputs: [{ type: 'uint256' }] + } +] as const; + +async function viemChainForWithdraw(chainId: number) { + const { + mainnet, + polygon, + arbitrum, + optimism, + base, + avalanche, + bsc, + gnosis, + polygonAmoy + } = await import('viem/chains'); + const map = { + 1: mainnet, + 137: polygon, + 42161: arbitrum, + 10: optimism, + 8453: base, + 43114: avalanche, + 56: bsc, + 100: gnosis, + 80002: polygonAmoy + } as const; + const c = map[chainId as keyof typeof map]; + if (!c) { + throw new Error( + `withdraw: chainId ${chainId} has no bundled viem chain config. Extend viemChainForWithdraw or use a supported chain.` + ); + } + return c; +} + +// --- withdraw --- +export const withdrawCommand: CommandModule = { + command: 'withdraw', + describe: 'Withdraw from an Aave v3 aToken position or ERC-4626 vault (dry-run by default)', + builder: (yargs) => + withBroadcast( + withWalletAndChain(yargs) + .option('position', { + type: 'string', + demandOption: true, + describe: 'Position token: Aave aToken address, or ERC-4626 vault (share token) address', + coerce: fileCoerce + }) + .option('amount', { + type: 'string', + demandOption: true, + describe: 'Underlying amount to withdraw (Aave), or max | partial underlying (ERC-4626). Use max for full exit.', + coerce: fileCoerce + }) + ), + handler: async (argv) => { + const walletName = (argv.wallet as string) || 'main'; + const positionAddr = String(argv.position || '') + .trim() + .toLowerCase() as `0x${string}`; + const amountArg = String(argv.amount || '').trim().toLowerCase(); + const broadcast = argv.broadcast as boolean; + + if (!positionAddr.startsWith('0x') || positionAddr.length !== 42) { + console.error( + JSON.stringify( + { ok: false, error: 'Invalid --position address (expected 0x + 40 hex chars).' }, + null, + 2 + ) + ); + process.exit(1); + } + + try { + const session = await loadWalletSession(walletName); + if (!session) throw new Error(`Wallet not found: ${walletName}`); + + const network = resolveNetwork((argv.chain as string) || session.chain || 'polygon'); + const { chainId } = network; + const walletAddress = session.walletAddress as `0x${string}`; + + const { createPublicClient, http, encodeFunctionData, maxUint256 } = await import('viem'); + const viemChain = await viemChainForWithdraw(chainId); + const publicClient = createPublicClient({ + chain: viemChain, + transport: http(getRpcUrl(network)) + }); + + const aaveMeta = await publicClient + .readContract({ + address: positionAddr, + abi: AAVE_ATOKEN_META_ABI, + functionName: 'POOL' + }) + .then(async (pool) => { + const underlying = await publicClient.readContract({ + address: positionAddr, + abi: AAVE_ATOKEN_META_ABI, + functionName: 'UNDERLYING_ASSET_ADDRESS' + }); + return { pool: pool as `0x${string}`, underlying: underlying as `0x${string}` }; + }) + .catch(() => null); + + let transactions: { to: `0x${string}`; value: bigint; data: `0x${string}` }[]; + let kind: 'aave' | 'erc4626'; + let summary: Record; + + if (aaveMeta) { + kind = 'aave'; + const underlyingDec = Number( + await publicClient.readContract({ + address: aaveMeta.underlying, + abi: ERC20_DECIMALS_ABI, + functionName: 'decimals' + }) + ); + const amountWei = + amountArg === 'max' || amountArg === 'all' + ? maxUint256 + : parseUnits(amountArg, underlyingDec); + + transactions = [ + { + to: aaveMeta.pool, + value: 0n, + data: encodeFunctionData({ + abi: AAVE_POOL_WITHDRAW_ABI, + functionName: 'withdraw', + args: [aaveMeta.underlying, amountWei, walletAddress] + }) + } + ]; + summary = { + protocol: 'aave', + poolAddress: aaveMeta.pool, + underlyingAsset: aaveMeta.underlying, + aToken: positionAddr, + amount: amountArg === 'max' || amountArg === 'all' ? 'max' : amountArg, + underlyingDecimals: underlyingDec + }; + } else { + const underlying = await publicClient + .readContract({ + address: positionAddr, + abi: ERC4626_ASSET_ABI, + functionName: 'asset' + }) + .catch(() => null); + + if (!underlying) { + throw new Error( + 'Could not treat --position as Aave aToken (POOL / UNDERLYING_ASSET_ADDRESS) or ERC-4626 vault (asset()). ' + + 'Pass the aToken or vault share contract you hold.' + ); + } + + kind = 'erc4626'; + const underlyingAddr = underlying as `0x${string}`; + const underlyingDec = Number( + await publicClient.readContract({ + address: underlyingAddr, + abi: ERC20_DECIMALS_ABI, + functionName: 'decimals' + }) + ); + + const shareBal = await publicClient.readContract({ + address: positionAddr, + abi: ERC20_BALANCE_OF_ABI, + functionName: 'balanceOf', + args: [walletAddress] + }); + + if (shareBal === 0n) { + throw new Error('ERC-4626 share balance is zero for this wallet on this chain.'); + } + + let sharesOut: bigint; + if (amountArg === 'max' || amountArg === 'all') { + sharesOut = shareBal; + } else { + const assetsWei = parseUnits(amountArg, underlyingDec); + sharesOut = await publicClient.readContract({ + address: positionAddr, + abi: ERC4626_CONVERT_TO_SHARES_ABI, + functionName: 'convertToShares', + args: [assetsWei] + }); + if (sharesOut > shareBal) { + throw new Error( + `Requested underlying withdraw exceeds vault shares (need ${sharesOut.toString()} shares, have ${shareBal.toString()}). Try --amount max.` + ); + } + } + + transactions = [ + { + to: positionAddr, + value: 0n, + data: encodeFunctionData({ + abi: ERC4626_REDEEM_ABI, + functionName: 'redeem', + args: [sharesOut, walletAddress, walletAddress] + }) + } + ]; + summary = { + protocol: 'erc4626', + vault: positionAddr, + underlyingAsset: underlyingAddr, + sharesRedeemed: sharesOut.toString(), + shareBalance: shareBal.toString(), + underlyingDecimals: underlyingDec + }; + } + + const targetContract = kind === 'aave' ? (summary.poolAddress as string) : positionAddr; + + if (!broadcast) { + console.log( + JSON.stringify( + { + ok: true, + dryRun: true, + walletName, + walletAddress, + chainId, + chain: network.name, + kind, + ...summary, + transactions, + note: + `Re-run with --broadcast to submit. If the session rejects the call, re-create the wallet with the pool/vault whitelisted: polygon-agent wallet create --contract ${targetContract}` + }, + bigintReplacer, + 2 + ) + ); + return; + } + + let result; + try { + result = await runDappClientTx({ + walletName, + chainId, + transactions, + broadcast, + preferNativeFee: false + }); + } catch (txErr) { + if ((txErr as Error).message?.includes('No signer supported')) { + throw new Error( + `Session does not permit calls to ${targetContract}. ` + + `Re-create the wallet session with: polygon-agent wallet create --contract ${targetContract}\n` + + `Original error: ${(txErr as Error).message}` + ); + } + throw txErr; + } + + console.log( + JSON.stringify( + { + ok: true, + walletName, + walletAddress, + chainId, + chain: network.name, + kind, + ...summary, + txHash: result.txHash, + explorerUrl: getExplorerUrl(network, result.txHash ?? '') + }, + bigintReplacer, + 2 + ) + ); + } catch (error) { + console.error( + JSON.stringify( + { + ok: false, + error: (error as Error).message, + stack: (error as Error).stack + }, + null, + 2 + ) + ); + process.exit(1); + } + } +}; + // --- x402-pay --- export const x402PayCommand: CommandModule = { command: 'x402-pay', diff --git a/packages/polygon-agent-cli/src/index.ts b/packages/polygon-agent-cli/src/index.ts index ade2f7b..476e8d4 100644 --- a/packages/polygon-agent-cli/src/index.ts +++ b/packages/polygon-agent-cli/src/index.ts @@ -16,6 +16,7 @@ import { sendNativeCommand, sendTokenCommand, swapCommand, + withdrawCommand, x402PayCommand } from './commands/operations.ts'; import { polymarketCommand } from './commands/polymarket.ts'; @@ -93,6 +94,7 @@ const parser = yargs(hideBin(process.argv)) .command(sendTokenCommand) .command(swapCommand) .command(depositCommand) + .command(withdrawCommand) .command(x402PayCommand) .command(agentCommand) .command(polymarketCommand); diff --git a/packages/polygon-agent-cli/src/lib/utils.ts b/packages/polygon-agent-cli/src/lib/utils.ts index 9807ea7..bf4c936 100644 --- a/packages/polygon-agent-cli/src/lib/utils.ts +++ b/packages/polygon-agent-cli/src/lib/utils.ts @@ -87,7 +87,8 @@ export function getRpcUrl(network: NetworkMetadata): string { /** Explorer URL for transaction */ export function getExplorerUrl(network: NetworkMetadata, txHash: string): string { - const base = network.blockExplorer?.rootUrl || `https://polygonscan.com`; + const raw = network.blockExplorer?.rootUrl || `https://polygonscan.com`; + const base = raw.replace(/\/+$/, ''); return `${base}/tx/${txHash}`; } diff --git a/skills/SKILL.md b/skills/SKILL.md index c65b57e..1d373b2 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -1,6 +1,6 @@ --- name: Polygon Agent -description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit to earn yield, register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." +description: "Complete Polygon agent toolkit for on-chain operations on Polygon. Use this skill whenever helping an agent set up a wallet, check balances, send or swap tokens, bridge assets, deposit or withdraw from yield (Aave aTokens, ERC-4626 vaults), register on-chain identity, submit or query reputation/feedback, or make x402 micropayments. Covers the full lifecycle: Sequence smart contract wallets, Trails DeFi actions, ERC-8004 identity + reputation, x402 payments. Single CLI entry point (`polygon-agent`), AES-256-GCM encrypted storage." --- # Polygon Agentic CLI @@ -105,6 +105,7 @@ polygon-agent send-native --to --amount [--broadcast] [--direct] polygon-agent send-token --symbol --to --amount [--token ] [--decimals ] [--broadcast] polygon-agent swap --from --to --amount [--to-chain ] [--slippage ] [--broadcast] polygon-agent deposit --asset --amount [--protocol aave|morpho] [--broadcast] +polygon-agent withdraw --position --amount [--chain ] [--broadcast] polygon-agent fund [--wallet ] [--token ] polygon-agent x402-pay --url --wallet [--method GET] [--body ] [--header Key:Value] ``` @@ -131,6 +132,7 @@ polygon-agent agent feedback --agent-id --value [--tag1 ] [--tag - **Fee preference** — auto-selects USDC over native POL when both available - **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address. - **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract ` +- **`withdraw`** — `--position` = aToken or ERC-4626 vault; `--amount` = `max` or underlying units (Aave / vault). Dry-run JSON includes `poolAddress` / `vault`. Broadcast needs session on the **same chain** as `--chain`, with pool/vault + underlying token whitelisted where the relayer touches them - **`x402-pay`** — probes endpoint for 402, smart wallet funds builder EOA with exact token amount, EOA signs EIP-3009 payment. Chain auto-detected from 402 response - **`send-native --direct`** — bypasses ValueForwarder contract for direct EOA transfer - **Session permissions** — without `--usdc-limit` etc., session gets bare-bones defaults and may not transact @@ -163,6 +165,7 @@ CLI commands output JSON (non-TTY). After running a command, always render the r | `send` / `send-token` / `send-native` | One-liner summary: amount, symbol, recipient. If broadcast, show tx hash as a code span and explorer URL as a link. | | `swap` | Summary: `X FROM → Y TO` with chain. If broadcast, show deposit tx hash + explorer link. | | `deposit` | Summary: amount, asset, protocol, pool address. If broadcast, show tx hash + explorer link. | +| `withdraw` | Summary: `kind` (aave / erc4626), position, amount, pool or vault. If broadcast, show tx hash + explorer link. | | `fund` | Show the `fundingUrl` as a clickable link with a brief instruction to open it. | | `wallet create` / `wallet list` | Wallet name, truncated address, chain in a small table or bullet list. | | `agent register` | Show agent name and tx hash as a code span with Polygonscan link. Remind user to retrieve `agentId` from the Registered event on the Logs tab. | @@ -183,7 +186,7 @@ For specific workflows, fetch and load the relevant sub-skill: | Use Case | Skill URL | |----------|-----------| | Polymarket prediction market trading | https://agentconnect.polygon.technology/polygon-polymarket/SKILL.md | -| DeFi — swap, deposit, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | +| DeFi — swap, deposit, withdraw, yield | https://agentconnect.polygon.technology/polygon-defi/SKILL.md | | x402 discovery & pay-per-call APIs | https://agentconnect.polygon.technology/polygon-discovery/SKILL.md | --- @@ -201,6 +204,8 @@ For specific workflows, fetch and load the relevant sub-skill: | `Invalid code: hash mismatch` | Wrong 6-digit code entered — retry (3 attempts allowed) | | `Relay request not found` | Session expired or already used — re-run `wallet create` (or `wallet create --print-url`) | | Deposit session rejected | Re-create wallet with `--contract ` | +| `withdraw` / broadcast: wrong chain or session rejects | Use `wallet create --chain ` and `--contract` for pool/vault + underlying ERC-20 on that chain; omit tight `--usdc-limit` if it blocks fee transfers | +| `Stored explicit session is missing pk` | Re-link: `wallet import --code …` after `wallet create` | | Wrong recipient in Trails widget | Run `polygon-agent fund` (do not construct the URL manually) | | `x402-pay`: no 402 response | Endpoint doesn't require x402 payment, or URL is wrong | | `x402-pay`: payment token mismatch | Chain/token in the 402 response differs from wallet — check `--wallet` points to the right chain | diff --git a/skills/polygon-defi/SKILL.md b/skills/polygon-defi/SKILL.md index 3751d99..0afcbff 100644 --- a/skills/polygon-defi/SKILL.md +++ b/skills/polygon-defi/SKILL.md @@ -129,6 +129,25 @@ polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast Vault/pool addresses are resolved dynamically from Trails — they are not hardcoded. The dry-run output includes `depositAddress` so you can inspect the exact contract before broadcasting. +## Withdraw (Aave aToken or ERC-4626 vault) + +Pass the **position token** you hold: an **Aave aToken** address, or a **Morpho / ERC-4626 vault** (share) address. The CLI resolves the Aave **Pool** via `POOL()` on the aToken, or uses `redeem` on the vault. Dry-run by default. + +```bash +# Full exit from an Aave position (aToken from balances output) +polygon-agent withdraw --position 0x68215b6533c47ff9f7125ac95adf00fe4a62f79e --amount max --chain mainnet + +# Partial Aave withdraw (underlying units, e.g. USDC) +polygon-agent withdraw --position --amount 0.5 --chain mainnet --broadcast + +# ERC-4626: max redeems all shares; partial amount is underlying units (convertToShares) +polygon-agent withdraw --position --amount max --chain polygon --broadcast +``` + +Whitelist the **pool** (Aave) or **vault** contract on the session if the wallet rejects the call (`polygon-agent wallet create --contract `). + +**Same chain as the transaction:** if you use `withdraw --chain mainnet`, create or refresh the session with **`wallet create --chain mainnet`** (not only Polygon defaults). Include **`--contract`** for the **pool** and for the **underlying ERC-20** on that chain (e.g. mainnet USDC `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) so fee / helper transfers are allowed. Tight **`--usdc-limit`** can block those — omit or relax for yield exits. + ### Session Whitelisting If the deposit is rejected with a session permission error, the pool's contract address needs to be whitelisted when creating the wallet session: From c718dbf731c389040bb87e922474ae191522dd0b Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Wed, 15 Apr 2026 19:34:23 -0400 Subject: [PATCH 3/8] fix: morpho vault deposit discovery and fix smart wallet usdc transfer limits --- packages/connector-ui/src/App.tsx | 4 ++-- packages/polygon-agent-cli/src/commands/operations.ts | 5 ++++- packages/polygon-agent-cli/src/commands/wallet.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/connector-ui/src/App.tsx b/packages/connector-ui/src/App.tsx index 2eeee8c..1b3163a 100644 --- a/packages/connector-ui/src/App.tsx +++ b/packages/connector-ui/src/App.tsx @@ -412,12 +412,12 @@ function App() { deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 183), permissions: [ ...basePermissions, - ...contractWhitelistPermissions, ...oneOffErc20Permissions, ...openTokenPermissions, ...dynamicTokenPermissions, ...nativeFeePermission, - ...feePermissions + ...feePermissions, + ...contractWhitelistPermissions ] }; diff --git a/packages/polygon-agent-cli/src/commands/operations.ts b/packages/polygon-agent-cli/src/commands/operations.ts index 2ca16c4..1dcd4e4 100644 --- a/packages/polygon-agent-cli/src/commands/operations.ts +++ b/packages/polygon-agent-cli/src/commands/operations.ts @@ -1124,7 +1124,10 @@ export const depositCommand: CommandModule = { let pools = ((earnRes as any)?.pools || []).filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any (p: any) => - p.isActive && p.chainId === chainId && p.token?.symbol?.toUpperCase() === assetSymbol + p.isActive && + p.chainId === chainId && + (p.token?.symbol?.toUpperCase() === assetSymbol || + p.token?.address?.toLowerCase() === asset.address.toLowerCase()) ); if (protocolFilter) { diff --git a/packages/polygon-agent-cli/src/commands/wallet.ts b/packages/polygon-agent-cli/src/commands/wallet.ts index 31e684a..0b41717 100644 --- a/packages/polygon-agent-cli/src/commands/wallet.ts +++ b/packages/polygon-agent-cli/src/commands/wallet.ts @@ -129,7 +129,7 @@ function applySessionPermissionParams(url: URL, argv: SessionPermissionArgs): vo } const nativeLimit = argv['native-limit']; - const usdcLimit = argv['usdc-limit'] || '50'; + const usdcLimit = argv['usdc-limit']; const usdtLimit = argv['usdt-limit']; if (nativeLimit) url.searchParams.set('nativeLimit', nativeLimit); url.searchParams.set('usdcLimit', usdcLimit); From 599cb69bef4fb638b17e4cf924edd356b8db07de Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Wed, 15 Apr 2026 22:36:06 -0400 Subject: [PATCH 4/8] feat: auto-resolve aToken and Vault positions by asset and protocol in withdraw command --- .../src/commands/operations.ts | 124 +++++++++++++++--- 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/packages/polygon-agent-cli/src/commands/operations.ts b/packages/polygon-agent-cli/src/commands/operations.ts index 1dcd4e4..c8fea0b 100644 --- a/packages/polygon-agent-cli/src/commands/operations.ts +++ b/packages/polygon-agent-cli/src/commands/operations.ts @@ -1452,10 +1452,17 @@ export const withdrawCommand: CommandModule = { withWalletAndChain(yargs) .option('position', { type: 'string', - demandOption: true, - describe: 'Position token: Aave aToken address, or ERC-4626 vault (share token) address', + describe: 'Position token: Aave aToken address, or ERC-4626 vault (share token) address. (Optional if --asset and --protocol are used)', coerce: fileCoerce }) + .option('asset', { + type: 'string', + describe: 'Asset symbol (e.g. USDC). Used with --protocol to auto-discover the position address.' + }) + .option('protocol', { + type: 'string', + describe: 'Filter by protocol (e.g. aave, morpho). Used with --asset to auto-discover the position address.' + }) .option('amount', { type: 'string', demandOption: true, @@ -1465,22 +1472,10 @@ export const withdrawCommand: CommandModule = { ), handler: async (argv) => { const walletName = (argv.wallet as string) || 'main'; - const positionAddr = String(argv.position || '') - .trim() - .toLowerCase() as `0x${string}`; const amountArg = String(argv.amount || '').trim().toLowerCase(); const broadcast = argv.broadcast as boolean; - - if (!positionAddr.startsWith('0x') || positionAddr.length !== 42) { - console.error( - JSON.stringify( - { ok: false, error: 'Invalid --position address (expected 0x + 40 hex chars).' }, - null, - 2 - ) - ); - process.exit(1); - } + const protocolFilter = (argv.protocol as string)?.toLowerCase(); + const assetSymbol = (argv.asset as string)?.toUpperCase(); try { const session = await loadWalletSession(walletName); @@ -1490,13 +1485,106 @@ export const withdrawCommand: CommandModule = { const { chainId } = network; const walletAddress = session.walletAddress as `0x${string}`; - const { createPublicClient, http, encodeFunctionData, maxUint256 } = await import('viem'); + const { createPublicClient, http, encodeFunctionData, maxUint256, parseUnits } = await import('viem'); const viemChain = await viemChainForWithdraw(chainId); const publicClient = createPublicClient({ chain: viemChain, transport: http(getRpcUrl(network)) }); + let positionAddr = String(argv.position || '').trim().toLowerCase() as `0x${string}`; + + if (!positionAddr && assetSymbol && protocolFilter) { + const asset = await getTokenConfig({ + chainId, + symbol: assetSymbol, + nativeSymbol: network.nativeToken?.symbol || 'POL' + }); + + const { TrailsApi } = await import('@0xtrails/api'); + const trailsApiKey = + process.env.TRAILS_API_KEY || + session.projectAccessKey || + process.env.SEQUENCE_PROJECT_ACCESS_KEY || + ''; + const trails = new TrailsApi(trailsApiKey, { + hostname: process.env.TRAILS_API_HOSTNAME + }); + + const earnRes = await trails.getEarnPools({ chainIds: [chainId] }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let pools = ((earnRes as any)?.pools || []).filter( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (p: any) => + p.isActive && + p.chainId === chainId && + (p.token?.symbol?.toUpperCase() === assetSymbol || + p.token?.address?.toLowerCase() === asset.address.toLowerCase()) + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + pools = pools.filter((p: any) => + p.protocol?.toLowerCase().includes(protocolFilter) + ); + + if (pools.length === 0) { + throw new Error( + `No active earn pools found for ${assetSymbol} on ${network.name} (protocol filter: ${protocolFilter}). Try passing --position explicitly.` + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + pools.sort((a: any, b: any) => b.tvl - a.tvl); + const pool = pools[0]; + + if (protocolFilter.includes('aave')) { + const AAVE_POOL_RESERVE_DATA_ABI = [{ + "inputs": [{"internalType": "address","name": "asset","type": "address"}], + "name": "getReserveData", + "outputs": [ + {"components": [ + {"components": [{"internalType": "uint256","name": "data","type": "uint256"}],"internalType": "struct DataTypes.ReserveConfigurationMap","name": "configuration","type": "tuple"}, + {"internalType": "uint128","name": "liquidityIndex","type": "uint128"}, + {"internalType": "uint128","name": "currentLiquidityRate","type": "uint128"}, + {"internalType": "uint128","name": "variableBorrowIndex","type": "uint128"}, + {"internalType": "uint128","name": "currentVariableBorrowRate","type": "uint128"}, + {"internalType": "uint128","name": "currentStableBorrowRate","type": "uint128"}, + {"internalType": "uint40","name": "lastUpdateTimestamp","type": "uint40"}, + {"internalType": "uint16","name": "id","type": "uint16"}, + {"internalType": "address","name": "aTokenAddress","type": "address"}, + {"internalType": "address","name": "stableDebtTokenAddress","type": "address"}, + {"internalType": "address","name": "variableDebtTokenAddress","type": "address"}, + {"internalType": "address","name": "interestRateStrategyAddress","type": "address"}, + {"internalType": "uint128","name": "accruedToTreasury","type": "uint128"}, + {"internalType": "uint128","name": "unbacked","type": "uint128"}, + {"internalType": "uint128","name": "isolationModeTotalDebt","type": "uint128"} + ],"internalType": "struct DataTypes.ReserveData","name": "","type": "tuple"} + ], + "stateMutability": "view", + "type": "function" + }]; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const reserveData: any = await publicClient.readContract({ + address: pool.depositAddress as `0x${string}`, + abi: AAVE_POOL_RESERVE_DATA_ABI, + functionName: 'getReserveData', + args: [asset.address as `0x${string}`] + }).catch(() => null); + + if (!reserveData || !reserveData.aTokenAddress) { + throw new Error(`Failed to resolve aToken address for ${assetSymbol} on Aave pool ${pool.depositAddress}. Try passing --position explicitly.`); + } + positionAddr = reserveData.aTokenAddress.toLowerCase() as `0x${string}`; + } else { + positionAddr = pool.depositAddress.toLowerCase() as `0x${string}`; + } + } + + if (!positionAddr.startsWith('0x') || positionAddr.length !== 42) { + throw new Error('Invalid or missing --position address (expected 0x + 40 hex chars). Provide --position or both --asset and --protocol.'); + } + const aaveMeta = await publicClient .readContract({ address: positionAddr, @@ -1561,7 +1649,7 @@ export const withdrawCommand: CommandModule = { if (!underlying) { throw new Error( - 'Could not treat --position as Aave aToken (POOL / UNDERLYING_ASSET_ADDRESS) or ERC-4626 vault (asset()). ' + + `Could not treat position (${positionAddr}) as Aave aToken (POOL / UNDERLYING_ASSET_ADDRESS) or ERC-4626 vault (asset()). ` + 'Pass the aToken or vault share contract you hold.' ); } From 1a2eee166a4ebfd56c4aa223713785f83511b98f Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Wed, 15 Apr 2026 22:42:33 -0400 Subject: [PATCH 5/8] Revert "fix: morpho vault deposit discovery and fix smart wallet usdc transfer limits" This reverts commit c718dbf731c389040bb87e922474ae191522dd0b. --- packages/connector-ui/src/App.tsx | 4 ++-- packages/polygon-agent-cli/src/commands/operations.ts | 5 +---- packages/polygon-agent-cli/src/commands/wallet.ts | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/connector-ui/src/App.tsx b/packages/connector-ui/src/App.tsx index 1b3163a..2eeee8c 100644 --- a/packages/connector-ui/src/App.tsx +++ b/packages/connector-ui/src/App.tsx @@ -412,12 +412,12 @@ function App() { deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 183), permissions: [ ...basePermissions, + ...contractWhitelistPermissions, ...oneOffErc20Permissions, ...openTokenPermissions, ...dynamicTokenPermissions, ...nativeFeePermission, - ...feePermissions, - ...contractWhitelistPermissions + ...feePermissions ] }; diff --git a/packages/polygon-agent-cli/src/commands/operations.ts b/packages/polygon-agent-cli/src/commands/operations.ts index c8fea0b..f49ea22 100644 --- a/packages/polygon-agent-cli/src/commands/operations.ts +++ b/packages/polygon-agent-cli/src/commands/operations.ts @@ -1124,10 +1124,7 @@ export const depositCommand: CommandModule = { let pools = ((earnRes as any)?.pools || []).filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any (p: any) => - p.isActive && - p.chainId === chainId && - (p.token?.symbol?.toUpperCase() === assetSymbol || - p.token?.address?.toLowerCase() === asset.address.toLowerCase()) + p.isActive && p.chainId === chainId && p.token?.symbol?.toUpperCase() === assetSymbol ); if (protocolFilter) { diff --git a/packages/polygon-agent-cli/src/commands/wallet.ts b/packages/polygon-agent-cli/src/commands/wallet.ts index 0b41717..31e684a 100644 --- a/packages/polygon-agent-cli/src/commands/wallet.ts +++ b/packages/polygon-agent-cli/src/commands/wallet.ts @@ -129,7 +129,7 @@ function applySessionPermissionParams(url: URL, argv: SessionPermissionArgs): vo } const nativeLimit = argv['native-limit']; - const usdcLimit = argv['usdc-limit']; + const usdcLimit = argv['usdc-limit'] || '50'; const usdtLimit = argv['usdt-limit']; if (nativeLimit) url.searchParams.set('nativeLimit', nativeLimit); url.searchParams.set('usdcLimit', usdcLimit); From 22e09c861e07b8216e48b4590dd817403f2900a4 Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Wed, 15 Apr 2026 22:53:05 -0400 Subject: [PATCH 6/8] docs: clarify morpho vault deposits via receipt token symbols --- .../public/skills/polygon-defi/SKILL.md | 11 ++++--- .../skills/polygon-defi/SKILL.md | 30 +++++-------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/packages/connector-ui/public/skills/polygon-defi/SKILL.md b/packages/connector-ui/public/skills/polygon-defi/SKILL.md index 0afcbff..b2edf43 100644 --- a/packages/connector-ui/public/skills/polygon-defi/SKILL.md +++ b/packages/connector-ui/public/skills/polygon-defi/SKILL.md @@ -108,16 +108,19 @@ interface PoolTokenInfo { Pool discovery uses `TrailsApi.getEarnPools` — picks the most liquid pool (highest TVL) for the asset on the current chain. No hardcoded addresses — the pool is resolved at runtime. +**Note on Morpho Vaults:** +Trails categorizes Morpho vaults by their *receipt* token symbol (e.g., `STEAKUSDC` or `gtUSDCp`), rather than the underlying token. To deposit into a Morpho vault, you must provide the vault's exact receipt token symbol as the `--asset`. +If you try to deposit using `--asset USDC`, it may fail to find the pool. In those cases, you can use the `swap` command to swap `USDC` directly for the vault's receipt token address. + ```bash # Dry-run — shows pool name, APY, TVL, and deposit address before committing -polygon-agent deposit --asset USDC --amount 0.3 +polygon-agent deposit --asset STEAKUSDC --amount 0.3 --protocol morpho # Execute — deposits into the highest-TVL active pool -polygon-agent deposit --asset USDC --amount 0.3 --broadcast +polygon-agent deposit --asset STEAKUSDC --amount 0.3 --protocol morpho --broadcast -# Filter by protocol +# Filter by protocol (Aave uses the underlying asset name) polygon-agent deposit --asset USDC --amount 0.3 --protocol aave --broadcast -polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast ``` ### Supported Protocols diff --git a/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md b/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md index 0afcbff..2ab310c 100644 --- a/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md +++ b/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md @@ -108,16 +108,19 @@ interface PoolTokenInfo { Pool discovery uses `TrailsApi.getEarnPools` — picks the most liquid pool (highest TVL) for the asset on the current chain. No hardcoded addresses — the pool is resolved at runtime. +**Note on Morpho Vaults:** +Trails categorizes Morpho vaults by their *receipt* token symbol (e.g., `STEAKUSDC` or `gtUSDCp`), rather than the underlying token. To deposit into a Morpho vault, you must provide the vault's exact receipt token symbol as the `--asset`. +If you try to deposit using `--asset USDC`, it may fail to find the pool. In those cases, you can use the `swap` command to swap `USDC` directly for the vault's receipt token address. + ```bash # Dry-run — shows pool name, APY, TVL, and deposit address before committing -polygon-agent deposit --asset USDC --amount 0.3 +polygon-agent deposit --asset STEAKUSDC --amount 0.3 --protocol morpho # Execute — deposits into the highest-TVL active pool -polygon-agent deposit --asset USDC --amount 0.3 --broadcast +polygon-agent deposit --asset STEAKUSDC --amount 0.3 --protocol morpho --broadcast -# Filter by protocol +# Filter by protocol (Aave uses the underlying asset name) polygon-agent deposit --asset USDC --amount 0.3 --protocol aave --broadcast -polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast ``` ### Supported Protocols @@ -129,25 +132,6 @@ polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast Vault/pool addresses are resolved dynamically from Trails — they are not hardcoded. The dry-run output includes `depositAddress` so you can inspect the exact contract before broadcasting. -## Withdraw (Aave aToken or ERC-4626 vault) - -Pass the **position token** you hold: an **Aave aToken** address, or a **Morpho / ERC-4626 vault** (share) address. The CLI resolves the Aave **Pool** via `POOL()` on the aToken, or uses `redeem` on the vault. Dry-run by default. - -```bash -# Full exit from an Aave position (aToken from balances output) -polygon-agent withdraw --position 0x68215b6533c47ff9f7125ac95adf00fe4a62f79e --amount max --chain mainnet - -# Partial Aave withdraw (underlying units, e.g. USDC) -polygon-agent withdraw --position --amount 0.5 --chain mainnet --broadcast - -# ERC-4626: max redeems all shares; partial amount is underlying units (convertToShares) -polygon-agent withdraw --position --amount max --chain polygon --broadcast -``` - -Whitelist the **pool** (Aave) or **vault** contract on the session if the wallet rejects the call (`polygon-agent wallet create --contract `). - -**Same chain as the transaction:** if you use `withdraw --chain mainnet`, create or refresh the session with **`wallet create --chain mainnet`** (not only Polygon defaults). Include **`--contract`** for the **pool** and for the **underlying ERC-20** on that chain (e.g. mainnet USDC `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) so fee / helper transfers are allowed. Tight **`--usdc-limit`** can block those — omit or relax for yield exits. - ### Session Whitelisting If the deposit is rejected with a session permission error, the pool's contract address needs to be whitelisted when creating the wallet session: From 83e7f2cf281390fbb019d78008a55245ca03fd1d Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Wed, 15 Apr 2026 23:11:04 -0400 Subject: [PATCH 7/8] fix: smart wallet usdc transfer limits and permissions order --- packages/connector-ui/src/App.tsx | 4 ++-- packages/polygon-agent-cli/src/commands/wallet.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/connector-ui/src/App.tsx b/packages/connector-ui/src/App.tsx index 2eeee8c..1b3163a 100644 --- a/packages/connector-ui/src/App.tsx +++ b/packages/connector-ui/src/App.tsx @@ -412,12 +412,12 @@ function App() { deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 183), permissions: [ ...basePermissions, - ...contractWhitelistPermissions, ...oneOffErc20Permissions, ...openTokenPermissions, ...dynamicTokenPermissions, ...nativeFeePermission, - ...feePermissions + ...feePermissions, + ...contractWhitelistPermissions ] }; diff --git a/packages/polygon-agent-cli/src/commands/wallet.ts b/packages/polygon-agent-cli/src/commands/wallet.ts index 31e684a..ba89bbe 100644 --- a/packages/polygon-agent-cli/src/commands/wallet.ts +++ b/packages/polygon-agent-cli/src/commands/wallet.ts @@ -129,10 +129,10 @@ function applySessionPermissionParams(url: URL, argv: SessionPermissionArgs): vo } const nativeLimit = argv['native-limit']; - const usdcLimit = argv['usdc-limit'] || '50'; + const usdcLimit = argv['usdc-limit']; const usdtLimit = argv['usdt-limit']; if (nativeLimit) url.searchParams.set('nativeLimit', nativeLimit); - url.searchParams.set('usdcLimit', usdcLimit); + if (usdcLimit) url.searchParams.set('usdcLimit', usdcLimit); if (usdtLimit) url.searchParams.set('usdtLimit', usdtLimit); const tokenLimits = (argv['token-limit'] || []) From d97921674df79dbb616c31de60b4ac1b8028689b Mon Sep 17 00:00:00 2001 From: Yash Devarshi Date: Thu, 16 Apr 2026 10:46:06 -0400 Subject: [PATCH 8/8] fix: restore connector skill URL sync (polygon-* public copies + husky) --- .husky/pre-commit | 19 +- .../connector-ui/public/polygon-defi/SKILL.md | 228 ++++++++++++ .../public/polygon-discovery/SKILL.md | 117 +++++++ .../public/polygon-polymarket/SKILL.md | 330 ++++++++++++++++++ 4 files changed, 690 insertions(+), 4 deletions(-) create mode 100644 packages/connector-ui/public/polygon-defi/SKILL.md create mode 100644 packages/connector-ui/public/polygon-discovery/SKILL.md create mode 100644 packages/connector-ui/public/polygon-polymarket/SKILL.md diff --git a/.husky/pre-commit b/.husky/pre-commit index 1941aca..a9909fd 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -9,8 +9,11 @@ # skill entry point for the agent. # # packages/connector-ui/public/skills/ -# Served at https://agentconnect.polygon.technology/skills/ — agents fetch -# skill files at runtime from this URL. +# Served at https://agentconnect.polygon.technology/skills/ — full tree. +# +# packages/connector-ui/public// +# Also served at https://agentconnect.polygon.technology//SKILL.md +# (e.g. /polygon-discovery/SKILL.md) to match the URLs in the root SKILL.md. # # packages/polygon-agent-cli/skills/ # Bundled inside the published npm package so skills are available @@ -41,15 +44,23 @@ function copyDir(src, dest) { // Sync root SKILL.md to connector-ui public root (served at /SKILL.md) fs.copyFileSync('skills/SKILL.md', 'packages/connector-ui/public/SKILL.md'); -// Sync to connector-ui (verbatim) +// Sync to connector-ui (verbatim — full tree under /skills/) copyDir('skills', 'packages/connector-ui/public/skills'); +// Also serve each sub-skill at the root level (e.g. /polygon-discovery/SKILL.md) +// to match the URLs referenced in the root SKILL.md +for (const entry of fs.readdirSync('skills', { withFileTypes: true })) { + if (entry.isDirectory()) { + copyDir(path.join('skills', entry.name), path.join('packages/connector-ui/public', entry.name)); + } +} + // Sync to CLI package (rewrite name field in root SKILL.md only) copyDir('skills', 'packages/polygon-agent-cli/skills'); const cliSkill = path.join('packages/polygon-agent-cli/skills/SKILL.md'); fs.writeFileSync(cliSkill, fs.readFileSync(cliSkill, 'utf8').replace('name: polygon-agent-kit', 'name: polygon-agent-cli')); " -git add packages/connector-ui/public/SKILL.md packages/connector-ui/public/skills packages/polygon-agent-cli/skills +git add packages/connector-ui/public/SKILL.md packages/connector-ui/public/skills packages/connector-ui/public/polygon-* packages/polygon-agent-cli/skills pnpm exec lint-staged pnpm run typecheck diff --git a/packages/connector-ui/public/polygon-defi/SKILL.md b/packages/connector-ui/public/polygon-defi/SKILL.md new file mode 100644 index 0000000..0afcbff --- /dev/null +++ b/packages/connector-ui/public/polygon-defi/SKILL.md @@ -0,0 +1,228 @@ +--- +name: polygon-defi +description: DeFi operations on Polygon using the Polygon Agent CLI. Covers same-chain token swaps, cross-chain bridging, and yield deposits into Aave v3 and Morpho vaults via Trails earn pool discovery. All commands dry-run by default — add --broadcast to execute. +--- + +# Polygon DeFi Skill + +## Swap Tokens (Same-Chain) + +```bash +# Dry-run — shows route and output amount +polygon-agent swap --from USDC --to USDT --amount 5 + +# Execute +polygon-agent swap --from USDC --to USDT --amount 5 --broadcast + +# Custom slippage (default 0.5%) +polygon-agent swap --from USDC --to USDT --amount 5 --slippage 0.005 --broadcast +``` + +## Bridge Tokens (Cross-Chain) + +```bash +# Bridge USDC from Polygon to Arbitrum +polygon-agent swap --from USDC --to USDC --amount 0.5 --to-chain arbitrum --broadcast + +# Bridge to other supported chains +polygon-agent swap --from USDC --to USDC --amount 1 --to-chain optimism --broadcast +polygon-agent swap --from USDC --to USDC --amount 1 --to-chain base --broadcast +polygon-agent swap --from USDC --to USDC --amount 1 --to-chain mainnet --broadcast +``` + +Valid `--to-chain` values: `polygon`, `amoy`, `mainnet`, `arbitrum`, `optimism`, `base`. + +## Query Earn Pools + +Use `getEarnPools` to discover live yield opportunities across protocols before deciding where to deposit. + +### HTTP + +```bash +curl --request POST \ + --url https://trails-api.sequence.app/rpc/Trails/GetEarnPools \ + --header 'Content-Type: application/json' \ + --data '{"chainIds": [137]}' +``` + +All request fields are optional — omit any you don't need to filter on. + +| Field | Type | Description | +|-------|------|-------------| +| `chainIds` | `number[]` | Filter by chain (e.g. `[137]` for Polygon mainnet) | +| `protocols` | `string[]` | Filter by protocol name, e.g. `["Aave"]`, `["Morpho"]` | +| `minTvl` | `number` | Minimum TVL in USD | +| `maxApy` | `number` | Maximum APY (useful to exclude outlier/at-risk pools) | + +### Fetch (agent code) + +The API key is the project access key already available to the agent (`SEQUENCE_PROJECT_ACCESS_KEY`). + +```typescript +const res = await fetch('https://trails-api.sequence.app/rpc/Trails/GetEarnPools', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ chainIds: [137] }), +}); +const { pools } = await res.json(); +``` + +### Response Schema + +```typescript +interface GetEarnPoolsResponse { + pools: EarnPool[]; + timestamp: string; // ISO-8601 fetch time + cached: boolean; +} + +interface EarnPool { + id: string; // "{protocol}-{chainId}-{address}" + name: string; // e.g. "USDC Market" + protocol: string; // "Aave" | "Morpho" + chainId: number; + apy: number; // annualised yield as a decimal percent + tvl: number; // USD + token: PoolTokenInfo; + depositAddress: string; // contract to approve/send to + isActive: boolean; + poolUrl?: string; + protocolUrl?: string; + wrappedTokenGatewayAddress?: string; // non-null for Aave native-token markets +} + +interface PoolTokenInfo { + symbol: string; + name: string; + address: string; + decimals: number; + logoUrl?: string; +} +``` + +> **Tip:** `wrappedTokenGatewayAddress` is set on Aave markets that accept a wrapped native token (WPOL, WETH). Pass this address instead of `depositAddress` when depositing POL/ETH directly. + +--- + +## Deposit to Earn Yield + +Pool discovery uses `TrailsApi.getEarnPools` — picks the most liquid pool (highest TVL) for the asset on the current chain. No hardcoded addresses — the pool is resolved at runtime. + +```bash +# Dry-run — shows pool name, APY, TVL, and deposit address before committing +polygon-agent deposit --asset USDC --amount 0.3 + +# Execute — deposits into the highest-TVL active pool +polygon-agent deposit --asset USDC --amount 0.3 --broadcast + +# Filter by protocol +polygon-agent deposit --asset USDC --amount 0.3 --protocol aave --broadcast +polygon-agent deposit --asset USDC --amount 0.3 --protocol morpho --broadcast +``` + +### Supported Protocols + +| Protocol | Encoding | Description | +|----------|----------|-------------| +| **Aave v3** | `supply(asset, amount, onBehalfOf, referralCode)` | Lending pool deposit | +| **Morpho** | `deposit(assets, receiver)` — ERC-4626 | Vault deposit | + +Vault/pool addresses are resolved dynamically from Trails — they are not hardcoded. The dry-run output includes `depositAddress` so you can inspect the exact contract before broadcasting. + +## Withdraw (Aave aToken or ERC-4626 vault) + +Pass the **position token** you hold: an **Aave aToken** address, or a **Morpho / ERC-4626 vault** (share) address. The CLI resolves the Aave **Pool** via `POOL()` on the aToken, or uses `redeem` on the vault. Dry-run by default. + +```bash +# Full exit from an Aave position (aToken from balances output) +polygon-agent withdraw --position 0x68215b6533c47ff9f7125ac95adf00fe4a62f79e --amount max --chain mainnet + +# Partial Aave withdraw (underlying units, e.g. USDC) +polygon-agent withdraw --position --amount 0.5 --chain mainnet --broadcast + +# ERC-4626: max redeems all shares; partial amount is underlying units (convertToShares) +polygon-agent withdraw --position --amount max --chain polygon --broadcast +``` + +Whitelist the **pool** (Aave) or **vault** contract on the session if the wallet rejects the call (`polygon-agent wallet create --contract `). + +**Same chain as the transaction:** if you use `withdraw --chain mainnet`, create or refresh the session with **`wallet create --chain mainnet`** (not only Polygon defaults). Include **`--contract`** for the **pool** and for the **underlying ERC-20** on that chain (e.g. mainnet USDC `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) so fee / helper transfers are allowed. Tight **`--usdc-limit`** can block those — omit or relax for yield exits. + +### Session Whitelisting + +If the deposit is rejected with a session permission error, the pool's contract address needs to be whitelisted when creating the wallet session: + +```bash +# 1. Dry-run first to get the depositAddress +polygon-agent deposit --asset USDC --amount 0.3 +# → note the depositAddress in output + +# 2. Re-create wallet session with that contract whitelisted +polygon-agent wallet create --contract + +# 3. Retry +polygon-agent deposit --asset USDC --amount 0.3 --broadcast +``` + +When creating a wallet specifically for yield, add `--contract` flags for all intended vaults upfront and omit `--usdc-limit`: + +```bash +polygon-agent wallet create \ + --contract 0x794a61358d6845594f94dc1db02a252b5b4814ad \ + --contract 0x781fb7f6d845e3be129289833b04d43aa8558c42 +``` + +### Yield Vault Contract Whitelist + +#### Polygon Mainnet (chainId 137) + +| Protocol | Asset | Address | +|----------|-------|---------| +| Aave V3 Pool (all markets) | USDC, USDT, WETH, WMATIC… | `0x794a61358d6845594f94dc1db02a252b5b4814ad` | +| Morpho Compound USDC | USDC | `0x781fb7f6d845e3be129289833b04d43aa8558c42` | +| Morpho Compound WETH | WETH | `0xf5c81d25ee174d83f1fd202ca94ae6070d073ccf` | +| Morpho Compound POL | POL | `0x3f33f9f7e2d7cfbcbdf8ea8b870a6e3d449664c2` | + +#### Katana (chainId 747474) — Morpho Vaults + +| Vault | Asset | TVL | Address | +|-------|-------|-----|---------| +| Gauntlet USDT | USDT | ~$97M | `0x1ecdc3f2b5e90bfb55ff45a7476ff98a8957388e` | +| Steakhouse Prime USDC | USDC | ~$54M | `0x61d4f9d3797ba4da152238c53a6f93fb665c3c1d` | +| Yearn OG ETH | WETH | ~$16M | `0xfade0c546f44e33c134c4036207b314ac643dc2e` | +| Yearn OG USDC | USDC | ~$16M | `0xce2b8e464fc7b5e58710c24b7e5ebfb6027f29d7` | +| Gauntlet USDC | USDC | ~$8M | `0xe4248e2105508fcbad3fe95691551d1af14015f7` | +| Yearn OG USDT | USDT | ~$8M | `0x8ed68f91afbe5871dce31ae007a936ebe8511d47` | +| Gauntlet WETH | WETH | ~$6M | `0xc5e7ab07030305fc925175b25b93b285d40dcdff` | +| Hyperithm vbUSDC Apex | USDC | ~$3M | `0xef77f8c53af95f3348cee0fb2a02ee02ab9cdca5` | + +--- + +## Full DeFi Flow Example + +```bash +# 1. Check balances +polygon-agent balances + +# 2. Swap POL → USDC +polygon-agent swap --from POL --to USDC --amount 1 --broadcast + +# 3. Deposit USDC into highest-TVL yield pool +polygon-agent deposit --asset USDC --amount 1 --broadcast +# → protocol: morpho (or aave, whichever has highest TVL at the time) +# → poolApy shown in dry-run output + +# 4. Bridge remaining USDC to Arbitrum +polygon-agent swap --from USDC --to USDC --amount 0.5 --to-chain arbitrum --broadcast +``` + +--- + +## Troubleshooting + +| Error | Cause | Fix | +|-------|-------|-----| +| `Deposit session rejected` | Pool contract not whitelisted in session | Re-create wallet with `--contract ` | +| `Protocol X not yet supported` | Trails returned a protocol other than aave/morpho | Use `polygon-agent swap` to obtain the yield-bearing token manually | +| `Fee option errors` | Wallet has insufficient balance | Run `polygon-agent balances` and fund the wallet | +| `swap`: no route found | Insufficient liquidity for the pair | Try a different amount or token pair | diff --git a/packages/connector-ui/public/polygon-discovery/SKILL.md b/packages/connector-ui/public/polygon-discovery/SKILL.md new file mode 100644 index 0000000..27509bb --- /dev/null +++ b/packages/connector-ui/public/polygon-discovery/SKILL.md @@ -0,0 +1,117 @@ +--- +name: polygon-discovery +description: x402 Bazaar — pay-per-call API services accessible via the Polygon Agent CLI. No API keys or subscriptions needed. Each call costs a small USDC amount drawn from the agent's smart wallet. Covers web search, news, AI image generation, Twitter/X data, code review, text summarization, sentiment analysis, and article extraction. +--- + +# x402 Bazaar Services + +Pay-per-call APIs accessible via `x402-pay`. No API keys or subscriptions — each call costs a small USDC amount drawn from your wallet. The CLI detects the 402 response, funds the exact amount, and retries automatically. + +**Catalog:** `GET https://x402-api.onrender.com/api/catalog?status=online` + +--- + +## Prerequisites — Check Before Any x402 Call + +Before running any `x402-pay` command, verify the wallet session exists and is funded: + +```bash +# Check if a wallet is configured +polygon-agent wallet list +``` + +**If no wallet is listed**, the smart session has not been created. Run through the complete setup flow before proceeding: + +1. `polygon-agent setup --name "MyAgent"` — creates EOA and Sequence project +2. `polygon-agent wallet create --usdc-limit 100` — opens browser for session approval; enter the 6-digit code when prompted +3. `polygon-agent wallet address` — get address, then fund via https://agentconnect.polygon.technology +4. `polygon-agent balances` — confirm USDC is available before calling any x402 endpoint + +**If a wallet exists but `balances` shows 0 USDC**, direct the user to fund it via the UI — `x402-pay` will fail with an EOA funding error otherwise. + +Once a funded wallet is confirmed, proceed with the x402 calls below. + +--- + +## Read Twitter/X Profile + +$0.005 USDC per call. + +> **Note:** The catalog proxy (`/api/call/99063826-...`) returns 401 or HTML for this service. +> Use the direct endpoint below instead. + +```bash +# Profile + recent tweets +polygon-agent x402-pay \ + --url "https://x402-api.onrender.com/api/twitter?user=" \ + --wallet main --method POST + +# Specific tweet +polygon-agent x402-pay \ + --url "https://x402-api.onrender.com/api/twitter?tweet=https://x.com/user/status/" \ + --wallet main --method POST +``` + +Returns: follower/following counts and tweet metrics. + +**Troubleshooting:** If the direct endpoint fails, check the live catalog for the current URL: +```bash +curl -s "https://x402-api.onrender.com/api/catalog?status=online" \ + | jq '.[] | select(.name | test("twitter"; "i"))' +``` + +--- + +## Generate an AI Image + +$0.02 USDC per call. Powered by Google Gemini. + +```bash +polygon-agent x402-pay \ + --url "https://x402-api.onrender.com/api/call/2998d205-94d9-4f7e-8f8a-201a090a5530?prompt=&size=512" \ + --wallet main --method GET +``` + +`size` options: `256`, `512`, `1024`. Returns JSON with `data_uri` (base64 PNG) for embedding. + +--- + +## Review Code for Bugs & Security + +$0.01 USDC per call. Powered by GPT-4o. + +```bash +polygon-agent x402-pay \ + --url "https://x402-api.onrender.com/api/call/7f21e675-9fdc-4ba3-9a8d-145c6ac703c7" \ + --wallet main \ + --body '{"code": "", "language": ""}' +``` + +Returns: bugs, security issues, performance problems, and style suggestions — each with line number, severity, and fix suggestion. Plus an overall quality score. + +--- + +## Other Services + +| Service | Price | Endpoint | Key param | +|---------|-------|----------|-----------| +| Web search (DuckDuckGo) | $0.005 | `9b0f5b5f-8e6c-4b55-a264-008e4e490c26` | `?q=&max=10` | +| Latest news (Google News) | $0.005 | `266d045f-bae2-4c71-9469-3638ec860fc4` | `?topic=&lang=en` | +| Summarize text (GPT-4o-mini) | $0.01 | `dd9b5098-700d-47a9-a41a-c9eae66ca49d` | `?text=&maxLength=200` | +| Article → Markdown | $0.005 | `87b50238-5b99-4521-b5e1-7515a9c1526d` | `?url=` | +| Sentiment analysis (GPT-4o-mini) | $0.005 | `66d68ca6-a8d9-41a3-b024-a3fac2f5c7ba` | `?text=` | + +All use GET via `polygon-agent x402-pay --url "https://x402-api.onrender.com/api/call/" --wallet main --method GET`. + +--- + +## How x402 Works + +1. CLI sends the request to the endpoint +2. Endpoint responds with `HTTP 402 Payment Required` + payment details +3. CLI automatically funds the builder EOA with the exact token amount from the smart wallet +4. EOA signs an EIP-3009 payment authorization +5. CLI retries the original request with the payment header +6. Response is returned — the whole flow is transparent to the agent + +Chain and token are auto-detected from the 402 response. No manual configuration needed. diff --git a/packages/connector-ui/public/polygon-polymarket/SKILL.md b/packages/connector-ui/public/polygon-polymarket/SKILL.md new file mode 100644 index 0000000..0cac0ff --- /dev/null +++ b/packages/connector-ui/public/polygon-polymarket/SKILL.md @@ -0,0 +1,330 @@ +--- +name: polymarket-skill +description: Place bets on Polymarket prediction markets using the Polygon Agent CLI. Browse markets, check prices, buy YES/NO positions, sell positions, manage orders. All commands are JSON output. Dry-run by default — always add --broadcast to execute. +--- + +# Polymarket Skill + +## Session Initialization + +Before any polymarket command, initialize the session. Read `~/.polygon-agent/builder.json` and export the access key: + +```bash +export SEQUENCE_PROJECT_ACCESS_KEY= +export SEQUENCE_INDEXER_ACCESS_KEY=$SEQUENCE_PROJECT_ACCESS_KEY +``` + +Also verify the Polymarket key is set: +```bash +polygon-agent polymarket proxy-wallet +``` +If this returns `ok: true` with an `eoaAddress` and `proxyWalletAddress`, the key is configured. If it errors, the user needs to run `set-key` (see Onboarding below). + +--- + +## Understanding the 3 Addresses + +Every Polymarket user has three addresses. Do not confuse them: + +| Name | What it is | Used for | +|------|-----------|---------| +| EOA | Private key owner. Shown as `eoaAddress` in CLI output | Signs transactions and CLOB orders. Holds POL for gas | +| Proxy Wallet | Shown as `proxyWalletAddress` in CLI output. This is what Polymarket shows as "your address" in the UI | Holds USDC.e and outcome tokens. The CLOB `maker` | +| Deposit Address | A cross-chain bridge ingress — only relevant for bridging from other chains | Ignore for Polygon-native usage | + +**For trading:** funds go from the Sequence smart wallet → proxy wallet → CLOB orders. The proxy wallet is the trading identity. + +--- + +## Onboarding: First-Time Setup + +### Option A — Using email login (Polymarket account) + +If the user has a Polymarket account via email login: + +**Step 1: Get the private key from Polymarket** +``` +1. Go to: https://reveal.magic.link/polymarket +2. Connect/authenticate with the same email used for Polymarket +3. Copy the exported private key (0x...) +``` + +**Step 2: Accept Terms of Service** +``` +1. Go to: https://polymarket.com +2. Connect wallet using the exported private key (import to MetaMask or similar) +3. Accept Terms of Service when prompted + — This is REQUIRED for CLOB order placement. Without it, orders will fail. +``` + +**Step 3: Import the key into the CLI** +```bash +polygon-agent polymarket set-key +``` +Output confirms the `eoaAddress` and `proxyWalletAddress`. + +**Step 3b: Confirm your addresses (show this to the user)** +```bash +polygon-agent polymarket proxy-wallet +``` +**Tell the user:** "Your EOA is `` — this needs POL for gas. Your Polymarket trading address (proxy wallet) is `` — this is where your USDC.e and outcome tokens live. The proxy wallet does not need POL. You must fund the EOA with POL and run approvals before trading." + +**Step 4: Fund the EOA with POL for gas** +```bash +# Check EOA address from set-key output, then send ~0.1 POL to it +polygon-agent send-native --to --amount 0.1 --broadcast +``` + +**Step 5: Set proxy wallet approvals (one-time, permanent)** +```bash +polygon-agent polymarket approve --broadcast +``` +This sends a transaction directly from the EOA (not through Polymarket's UI), so the EOA must hold POL for gas. This is different from trading on polymarket.com, where their UI sponsors gas for you. + +### Option B — Using the builder EOA (no Polymarket account needed) + +If the user has done `polygon-agent setup` already, the builder EOA can be used directly. Skip `set-key`. + +**Step 1: Confirm addresses (show this to the user)** +```bash +polygon-agent polymarket proxy-wallet +``` +**Tell the user:** "Your EOA is `` — this needs POL for gas. Your Polymarket trading address (proxy wallet) is `` — this is where your USDC.e and outcome tokens live. The proxy wallet does not need POL. You must accept Polymarket ToS, fund the EOA with POL, and run approvals before trading." + +**Step 2: Accept Terms of Service (required — CLOB orders will fail without this)** +``` +1. Go to https://polymarket.com +2. Connect with the EOA wallet address shown above +3. Accept Terms of Service when prompted +``` + +**Step 3: Fund the EOA with POL for gas** +```bash +polygon-agent send-native --to --amount 0.1 --broadcast +``` + +**Step 4: Set proxy wallet approvals (one-time)** +```bash +polygon-agent polymarket approve --broadcast +``` +This sends a transaction directly from the EOA (not through Polymarket's UI), so the EOA must hold POL for gas. + +--- + +## Commands + +### Browse Markets + +```bash +# List top markets by volume +polygon-agent polymarket markets + +# Search by keyword +polygon-agent polymarket markets --search "bitcoin" --limit 10 + +# Paginate +polygon-agent polymarket markets --limit 20 --offset 20 +``` + +Key output fields per market: +- `conditionId` — the ID needed for all trading commands +- `question` — what the market is asking +- `yesPrice` / `noPrice` — current probability (0 to 1, e.g. `0.65` = 65%) +- `negRisk` — if `true`, set neg-risk approvals before trading this market +- `endDate` — when the market resolves + +### Get a Single Market + +```bash +polygon-agent polymarket market +``` + +Use this to confirm prices and token IDs before placing an order. + +### Show Proxy Wallet Address + +```bash +polygon-agent polymarket proxy-wallet +``` + +Confirms which EOA and proxy wallet are active. The proxy wallet is where USDC.e and tokens are held. + +### Set Approvals (One-Time) + +```bash +# Standard markets +polygon-agent polymarket approve --broadcast + +# Neg-risk markets (if you see negRisk: true on any market you want to trade) +polygon-agent polymarket approve --neg-risk --broadcast +``` + +Run once per EOA. Permanent on-chain — no need to repeat unless enabling neg-risk. +**Dry-run (no --broadcast) shows what will be approved without executing.** + +### Buy a Position + +```bash +# Dry-run first — always check before executing +polygon-agent polymarket clob-buy YES|NO + +# Execute — funds proxy wallet from smart wallet, then places order +polygon-agent polymarket clob-buy YES|NO --broadcast + +# If proxy wallet already has USDC.e (skip the funding step) +polygon-agent polymarket clob-buy YES|NO --skip-fund --broadcast + +# Limit order — fill only at this price or better +polygon-agent polymarket clob-buy YES --price 0.45 --broadcast +``` + +**How it works:** +1. Smart wallet transfers `usdcAmount` USDC.e to the proxy wallet (Sequence tx, USDC.e fee) +2. Posts CLOB BUY order: maker=proxy wallet, signer=EOA (off-chain, no gas) +3. Tokens arrive in proxy wallet on fill + +**Order types:** +- No `--price`: FOK market order (fill entirely or cancel) +- `--fak`: FAK market order (partial fills allowed) +- `--price 0.x`: GTC limit order (stays open until filled or cancelled) + +**Minimum order size: $1 USDC.** The CLOB rejects marketable BUY orders below $1. If the fund step runs but the order is rejected, the USDC.e stays in the proxy wallet — use `--skip-fund` on the retry. + +### Sell a Position + +```bash +# Dry-run first +polygon-agent polymarket sell YES|NO + +# Execute +polygon-agent polymarket sell YES|NO --broadcast + +# Limit sell +polygon-agent polymarket sell YES --price 0.80 --broadcast +``` + +`` is the number of outcome tokens (not USD). Get share count from `positions`. +Selling is pure off-chain — no gas, no on-chain tx. + +### Check Positions + +```bash +polygon-agent polymarket positions +``` + +Shows all open positions in the proxy wallet with current value, P&L, and outcome. + +### Check Open Orders + +```bash +polygon-agent polymarket orders +``` + +Lists GTC limit orders that are still open (FOK/FAK orders are never "open" — they fill or cancel immediately). + +### Cancel an Order + +```bash +polygon-agent polymarket cancel +``` + +Get `orderId` from the `orders` command or from the `orderId` field in `clob-buy` output. + +--- + +## Full Autonomous Trading Flow + +This is the exact sequence to go from zero to a filled trade: + +```bash +# ── SETUP (run once per EOA) ──────────────────────────────────────────── + +# 1. Import your Polymarket private key +# (get it from https://reveal.magic.link/polymarket after email login) +polygon-agent polymarket set-key 0x +# → save eoaAddress and proxyWalletAddress from output + +# 2. Fund the EOA with POL for gas +polygon-agent send-native --to --amount 0.1 --broadcast + +# 3. Set proxy wallet approvals (one-time) +polygon-agent polymarket approve --broadcast +# → save approveTxHash, wait for confirmation + +# ── FIND A MARKET ──────────────────────────────────────────────────────── + +# 4. Search for markets +polygon-agent polymarket markets --search "fed rate" --limit 10 + +# 5. Get details on a specific market +polygon-agent polymarket market 0x +# → check: yesPrice, noPrice, negRisk, endDate +# → if negRisk: true → run approve --neg-risk --broadcast first + +# ── ENTER A POSITION ──────────────────────────────────────────────────── + +# 6. Dry-run to confirm everything +polygon-agent polymarket clob-buy 0x YES 5 +# → review: currentPrice, proxyWalletAddress, flow + +# 7. Execute +polygon-agent polymarket clob-buy 0x YES 5 --broadcast +# → save orderId, check orderStatus === "matched" + +# ── MANAGE ────────────────────────────────────────────────────────────── + +# 8. Check your positions +polygon-agent polymarket positions +# → review: size (shares), curPrice, cashPnl, title, outcome + +# 9. Sell when ready +polygon-agent polymarket sell 0x YES --broadcast +# → orderStatus === "matched" means USDC.e is back in proxy wallet +``` + +--- + +## Decision Logic for an Autonomous Agent + +When deciding whether to buy: +1. Check `positions` — avoid doubling up on already-held positions +2. Check `markets` — use `yesPrice`/`noPrice` as probability inputs +3. Check `negRisk` — if `true`, verify neg-risk approvals were set +4. Check proxy wallet USDC.e balance before buying (use `proxy-wallet` to get address, then check balance externally or via `balances`) +5. Use `--skip-fund` if the proxy wallet already has enough USDC.e from a previous `clob-buy` +6. Always dry-run first, then broadcast + +When deciding whether to sell: +1. Get current `size` (shares) from `positions` +2. Use `curPrice` vs `avgPrice` to assess profit/loss +3. Market sell (`sell --broadcast`) for immediate exit +4. Limit sell (`--price 0.x --broadcast`) to wait for a better price + +--- + +## Troubleshooting + +| Error | Cause | Fix | +|-------|-------|-----| +| `No EOA key found` | `set-key` not run | Run `polygon-agent polymarket set-key ` | +| `Could not create api key` (stderr) | ToS not accepted | Visit polymarket.com, connect EOA wallet, accept terms. This error in **stderr** is non-fatal — the CLI retries with `deriveApiKey` and may still succeed. | +| `CLOB order error: not authorized` | ToS not accepted | Same as above — fatal for order posting | +| `insufficient funds for gas` | EOA has no POL | `polygon-agent send-native --to --amount 0.1 --broadcast` | +| `execution reverted: must be called be owner` | Old code calling proxy directly | Upgrade CLI — fixed in current version (calls factory) | +| `Market not found` | Low-volume or closed market | Market may have resolved; try `--search` with different terms | +| `Market has no tokenIds` | Closed market | Check `endDate` — market resolved | +| `orderStatus: "unmatched"` on FOK | No liquidity at market price | Try `--fak` for partial fill, or `--price 0.x` for limit order | +| `invalid amount for a marketable BUY order ($X), min size: $1` | Amount below CLOB minimum | Use at least $1. If USDC.e was already funded, retry with `--skip-fund` | +| `Wallet not found: main` | No Sequence wallet | Run `polygon-agent wallet create` | + +--- + +## Key Facts for Agents + +- **All commands are dry-run by default.** `approve`, `clob-buy`, `sell` do nothing without `--broadcast`. +- **`clob-buy` transfers USDC.e from the smart wallet to the proxy wallet automatically** (unless `--skip-fund`). +- **Positions live in the proxy wallet**, not the Sequence smart wallet. `positions` queries the proxy wallet. +- **Approvals are one-time.** Don't run `approve` before every trade — only once per EOA (and once more if enabling neg-risk). +- **Sell is free.** No gas, no on-chain tx. Selling via CLOB is a signed off-chain message only. +- **`orderStatus: "matched"`** means the trade filled. `"unmatched"` means FOK failed (no liquidity). +- **The proxy wallet address never changes.** It is deterministic from the EOA via CREATE2.