diff --git a/packages/connector-ui/src/App.tsx b/packages/connector-ui/src/App.tsx index 1b3163a..f1a13ca 100644 --- a/packages/connector-ui/src/App.tsx +++ b/packages/connector-ui/src/App.tsx @@ -124,7 +124,7 @@ const USE_CASES: { label: string; display: string; icon: ElementType }[] = [ { label: 'Automate yield strategies', display: - 'Deposit USDC into the highest-TVL lending vault on Polygon or Katana with TVL above $100M and report the APY and pool address. Then set up a daily cron job to automatically re-evaluate and deposit into the best vault each morning.', + 'Deposit USDC into the highest-yield active lending vault on Polygon and report the APY and pool address. Then set up a daily cron job to automatically re-evaluate and deposit into the best vault each morning.', icon: TrendingUp } ]; diff --git a/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md b/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md index 6601419..661bf00 100644 --- a/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md +++ b/packages/polygon-agent-cli/skills/polygon-defi/SKILL.md @@ -106,7 +106,9 @@ interface PoolTokenInfo { ## 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. +Pool discovery uses `TrailsApi.getEarnPools` — picks the most liquid pool (highest TVL) for the asset. Only Polygon mainnet (chainId 137) is supported. No hardcoded addresses — the pool is resolved at runtime. + +**Gas requirement:** The wallet needs POL for gas, or a session created with `--usdc-limit` to enable USDC paymaster. If the wallet has no POL, create the session with `--usdc-limit 5`. When USDC paymaster is active and the deposit amount would consume the full balance, the CLI auto-reserves 0.05 USDC for gas and prints a note. ```bash # Dry-run — shows pool name, APY, TVL, and deposit address before committing @@ -181,19 +183,6 @@ Common token contracts on Polygon mainnet (already auto-whitelisted in sessions | 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 @@ -216,11 +205,32 @@ polygon-agent swap --from USDC --to USDC --amount 0.5 --to-chain arbitrum --broa --- +## wallet create — Key Options + +| Flag | Purpose | +|------|---------| +| `--usdc-limit ` | Enable USDC gas paymaster. Required when the wallet has no POL. Recommended: `--usdc-limit 5`. | +| `--force` | Replace an existing session without prompting. By default, re-creating a session is blocked if one already exists — the old wallet balance is not accessible from a new session. | +| `--contract ` | Whitelist an additional contract (repeatable). Use this if a deposit is rejected due to a missing contract permission. | + +```bash +# New session with USDC gas and deposit contracts pre-whitelisted +polygon-agent wallet create --usdc-limit 5 + +# Replace an existing session +polygon-agent wallet create --force --usdc-limit 5 +``` + +--- + ## Troubleshooting | Error | Cause | Fix | |-------|-------|-----| -| `Deposit session rejected` | Pool or token contract not whitelisted | Re-create wallet with `--contract --contract ` (both required) | +| `Insufficient : wallet has X` | Balance too low for the requested deposit amount | Run `polygon-agent balances` and adjust `--amount` | +| `Wallet has no POL for gas` | No native gas and no USDC paymaster | Fund with POL (`polygon-agent fund`) or re-create session with `--usdc-limit 5` | +| `Transaction rejected by relay` | Session permissions missing for pool or token contract | Re-create with `--contract --contract ` | +| `Unable to pay gas` | No usable fee token found | Fund with POL or add `--usdc-limit 5` to session | +| `Wallet already exists` | Re-creating would orphan the old session | Use `--force` only after confirming old wallet funds are swept or unneeded | | `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/polygon-agent-cli/src/commands/operations.ts b/packages/polygon-agent-cli/src/commands/operations.ts index 1492e7c..ff23848 100644 --- a/packages/polygon-agent-cli/src/commands/operations.ts +++ b/packages/polygon-agent-cli/src/commands/operations.ts @@ -357,12 +357,8 @@ export const balancesCommand: CommandModule = { // --- fund --- export const fundCommand: CommandModule = { command: 'fund', - describe: 'Open Trails widget to fund wallet', - builder: (yargs) => - withWalletAndChain(yargs).option('token', { - type: 'string', - describe: 'Fund token address' - }), + describe: 'Get funding URL for wallet', + builder: (yargs) => withWalletAndChain(yargs), handler: async (argv) => { const walletName = argv.wallet as string; @@ -374,10 +370,7 @@ export const fundCommand: CommandModule = { const walletAddress = session.walletAddress; const chainId = session.chainId || 137; - const toToken = (argv.token as string) || '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359'; - const apiKey = process.env.SEQUENCE_PROJECT_ACCESS_KEY || ''; - - const fundingUrl = `https://demo.trails.build/?mode=swap&toAddress=${walletAddress}&toChainId=${chainId}&toToken=${toToken}&apiKey=${apiKey}&theme=light`; + const fundingUrl = `https://wallet.polygon.technology`; if (isTTY()) { await inkRender( @@ -392,7 +385,7 @@ export const fundCommand: CommandModule = { walletAddress, chainId, fundingUrl, - message: 'Open the funding URL in your browser to fund your wallet via Trails.' + message: `Visit ${fundingUrl} to fund your wallet (${walletAddress}).` }, null, 2 @@ -1083,7 +1076,7 @@ export const depositCommand: CommandModule = { handler: async (argv) => { const walletName = (argv.wallet as string) || 'main'; const assetSymbol = ((argv.asset as string) || 'USDC').toUpperCase(); - const amountArg = argv.amount as string; + let amountArg = argv.amount as string; const protocolFilter = argv.protocol as string | undefined; const broadcast = argv.broadcast as boolean; @@ -1128,9 +1121,10 @@ export const depositCommand: CommandModule = { if (pools.length === 0) { throw new Error( - `No active earn pools found for ${assetSymbol} on ${network.name}` + - (protocolFilter ? ` (protocol filter: ${protocolFilter})` : '') + - `. Try 'polygon-agent swap --from ${assetSymbol} --to ' as an alternative.` + `No active ${assetSymbol} earn pools found on ${network.name}` + + (protocolFilter ? ` (protocol: ${protocolFilter})` : '') + + `. Confirm wallet state: polygon-agent balances. ` + + `Alternative: polygon-agent swap --from ${assetSymbol} --to .` ); } @@ -1139,7 +1133,57 @@ export const depositCommand: CommandModule = { const pool = pools[0]; const proto = (pool.protocol || '').toLowerCase(); - const { encodeFunctionData, parseUnits: viemParseUnits } = await import('viem'); + const { + encodeFunctionData, + parseUnits: viemParseUnits, + formatUnits: viemFormatUnits, + createPublicClient, + http + } = await import('viem'); + // Pre-flight: verify balance and auto-reserve gas buffer when wallet has no native token + try { + const viemChain = await viemChainForDeposit(chainId); + const publicClient = createPublicClient({ chain: viemChain, transport: http() }); + const [usdcBal, nativeBal] = await Promise.all([ + publicClient.readContract({ + address: asset.address as `0x${string}`, + abi: ERC20_BALANCE_OF_ABI, + functionName: 'balanceOf', + args: [walletAddress as `0x${string}`] + }), + publicClient.getBalance({ address: walletAddress as `0x${string}` }) + ]); + const requestedUnits = viemParseUnits(amountArg, asset.decimals); + const GAS_RESERVE = viemParseUnits('0.05', asset.decimals); + if (usdcBal < requestedUnits) { + const available = viemFormatUnits(usdcBal, asset.decimals); + throw new Error( + `Insufficient ${assetSymbol}: wallet has ${available} ${assetSymbol}, deposit requires ${amountArg}. ` + + `Run: polygon-agent balances` + ); + } + if (nativeBal === 0n && requestedUnits + GAS_RESERVE > usdcBal) { + // Wallet has no POL — USDC paymaster will pay gas; auto-reduce to leave 0.05 buffer + const adjusted = usdcBal - GAS_RESERVE; + if (adjusted <= 0n) { + throw new Error( + `Insufficient ${assetSymbol} for deposit plus 0.05 gas reserve. ` + + `Fund with POL for native gas: polygon-agent fund` + ); + } + amountArg = viemFormatUnits(adjusted, asset.decimals); + process.stderr.write( + `Note: reduced deposit to ${amountArg} ${assetSymbol} (0.05 reserved for USDC gas)\n` + ); + } + } catch (e) { + if ((e as Error).message?.match(/^Insufficient/)) throw e; + // RPC unreachable — warn and continue + process.stderr.write( + `Warning: balance pre-flight check skipped (${(e as Error).message})\n` + ); + } + const amountUnits = viemParseUnits(amountArg, asset.decimals); const ERC20_APPROVE_ABI = [ @@ -1267,11 +1311,31 @@ export const depositCommand: CommandModule = { preferNativeFee: false }); } catch (txErr) { - if ((txErr as Error).message?.includes('No signer supported')) { + const txMsg = (txErr as Error).message || ''; + if (txMsg.includes('No signer supported')) { throw new Error( `Session does not permit calls to ${pool.depositAddress} (${pool.protocol} pool) or ${asset.address} (${assetSymbol} approve). ` + `Re-create the wallet session with: polygon-agent wallet create --contract ${asset.address} --contract ${pool.depositAddress}\n` + - `Original error: ${(txErr as Error).message}` + `Original error: ${txMsg}` + ); + } + if (txMsg.includes('Identity signers not found') || txMsg.includes('signers not found')) { + throw new Error( + `Wallet has no POL for gas and no USDC paymaster is configured. ` + + `Fund with POL: polygon-agent fund\n` + + `Or enable USDC gas: polygon-agent wallet create --usdc-limit 5\n` + + `Original error: ${txMsg}` + ); + } + if ( + txMsg.includes('Request aborted') || + txMsg.includes('AbortedError') || + txMsg.includes('code 1005') + ) { + throw new Error( + `Transaction rejected by relay — likely a session permission issue. ` + + `Re-create the wallet session: polygon-agent wallet create --contract ${asset.address} --contract ${pool.depositAddress}\n` + + `Original error: ${txMsg}` ); } throw txErr; @@ -1400,10 +1464,10 @@ const ERC4626_REDEEM_ABI = [ } ] as const; -async function viemChainForWithdraw(chainId: number) { +async function viemChainMap() { const { mainnet, polygon, arbitrum, optimism, base, avalanche, bsc, gnosis, polygonAmoy } = await import('viem/chains'); - const map = { + return { 1: mainnet, 137: polygon, 42161: arbitrum, @@ -1413,7 +1477,21 @@ async function viemChainForWithdraw(chainId: number) { 56: bsc, 100: gnosis, 80002: polygonAmoy - } as const; + } as const satisfies Record; +} + +async function viemChainForDeposit(chainId: number) { + const map = await viemChainMap(); + const c = map[chainId as keyof typeof map]; + if (!c) + throw new Error( + `deposit: chainId ${chainId} has no bundled viem chain config for pre-flight check.` + ); + return c; +} + +async function viemChainForWithdraw(chainId: number) { + const map = await viemChainMap(); const c = map[chainId as keyof typeof map]; if (!c) { throw new Error( diff --git a/packages/polygon-agent-cli/src/lib/dapp-client.ts b/packages/polygon-agent-cli/src/lib/dapp-client.ts index f842531..6c342ec 100644 --- a/packages/polygon-agent-cli/src/lib/dapp-client.ts +++ b/packages/polygon-agent-cli/src/lib/dapp-client.ts @@ -376,7 +376,8 @@ export async function runDappClientTx({ if (paymentAddress && erc20Token) { const decimals = typeof erc20Token.decimals === 'number' ? erc20Token.decimals : 6; - const feeValue = decimals >= 2 ? 10 ** (decimals - 2) : 1; + // 0.1 token units as fee cap (e.g. 0.1 USDC = 100000 for 6 decimals) + const feeValue = decimals >= 1 ? 10 ** (decimals - 1) : 1; feeOpt = { token: erc20Token, to: paymentAddress, @@ -399,7 +400,11 @@ export async function runDappClientTx({ const feeOptions = await client.getFeeOptions(chainId, transactions as any); feeOpt = feeOptions?.[0]; } catch (e) { - throw new Error(`Unable to determine fee option: ${(e as Error)?.message}`); + throw new Error( + `Unable to pay gas: wallet has no POL (native gas) and no usable fee token. ` + + `Fund with POL: polygon-agent fund, or enable USDC gas: polygon-agent wallet create --usdc-limit 5. ` + + `Technical: ${(e as Error)?.message}` + ); } } diff --git a/skills/polygon-defi/SKILL.md b/skills/polygon-defi/SKILL.md index 6601419..fd4cfe5 100644 --- a/skills/polygon-defi/SKILL.md +++ b/skills/polygon-defi/SKILL.md @@ -106,7 +106,9 @@ interface PoolTokenInfo { ## 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. +Pool discovery uses `TrailsApi.getEarnPools` — picks the most liquid pool (highest TVL) for the asset on the requested chain. No hardcoded addresses — the pool is resolved at runtime. Supported chains: Polygon, Base, Arbitrum, Optimism, Ethereum mainnet (any chain Trails indexes). + +**Gas requirement:** The wallet needs POL for gas, or a session created with `--usdc-limit` to enable USDC paymaster. If the wallet has no POL, create the session with `--usdc-limit 5`. When USDC paymaster is active and the deposit amount would consume the full balance, the CLI auto-reserves 0.05 USDC for gas and prints a note. ```bash # Dry-run — shows pool name, APY, TVL, and deposit address before committing @@ -181,19 +183,6 @@ Common token contracts on Polygon mainnet (already auto-whitelisted in sessions | 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 @@ -216,11 +205,27 @@ polygon-agent swap --from USDC --to USDC --amount 0.5 --to-chain arbitrum --broa --- +## wallet create — Key Options + +| Flag | Purpose | +|------|---------| +| `--usdc-limit ` | Enable USDC gas paymaster. Required when the wallet has no POL. Recommended: `--usdc-limit 5`. | +| `--contract ` | Whitelist an additional contract (repeatable). Use this if a deposit is rejected due to a missing contract permission. | + +```bash +# New session with USDC gas enabled +polygon-agent wallet create --usdc-limit 5 +``` + +--- + ## Troubleshooting | Error | Cause | Fix | |-------|-------|-----| -| `Deposit session rejected` | Pool or token contract not whitelisted | Re-create wallet with `--contract --contract ` (both required) | +| `Insufficient : wallet has X` | Balance too low for the requested deposit amount | Run `polygon-agent balances` and adjust `--amount` | +| `Wallet has no POL for gas` | No native gas and no USDC paymaster | Fund with POL (`polygon-agent fund`) or re-create session with `--usdc-limit 5` | +| `Transaction rejected by relay` | Session permissions missing for pool or token contract | Re-create with `--contract --contract ` | +| `Unable to pay gas` | No usable fee token found | Fund with POL or add `--usdc-limit 5` to session | | `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 |