From zerion-agent
Deposits tokens from any chain into Polygon DeFi vaults like Aave, Morpho, or ERC-4626 using Trails SDK for cross-chain bridging and Zerion CLI for wallet funding and portfolio verification.
npx claudepluginhub zeriontech/zerion-ai --plugin zerion-agentThis skill is limited to using the following tools:
Bridge tokens from any chain and deposit directly into a DeFi vault on Polygon in a single intent, powered by [Trails](https://docs.trails.build). Trails handles routing, cross-chain settlement, and the vault deposit call atomically. Pair with the Zerion CLI to fund the wallet and verify the resulting vault position.
Handles cross-chain token swaps to/from Polygon (chainId 137) using Trails SDK, with Zerion CLI for wallet funding and portfolio checks. Supports React/Next.js widgets, headless hooks, and Node.js server-side APIs for bridging/swapping tokens.
Builds unsigned DeFi transactions from natural language intents across EVM chains like Ethereum, Arbitrum, Base, Optimism, Polygon. Supports send, swap, stake, lend on Aave, Uniswap, Lido, etc.
Discovers and executes OKX-aggregated DeFi investments for yield farming, lending, borrowing, staking, liquidity pools, and APY/TVL analysis without naming specific DApps. Routes to DApp-specific skills if named.
Share bugs, ideas, or general feedback.
Bridge tokens from any chain and deposit directly into a DeFi vault on Polygon in a single intent, powered by Trails. Trails handles routing, cross-chain settlement, and the vault deposit call atomically. Pair with the Zerion CLI to fund the wallet and verify the resulting vault position.
Visit https://dashboard.trails.build to create an account and generate a key.
# Widget or hooks (React / Next.js) + calldata encoding
npm install 0xtrails viem
# Direct API (Node.js / backend)
npm install @0xtrails/api viem
Polygon chain ID: 137
Polygon chain name (widget/hooks): "polygon"
Common deposit tokens on Polygon:
| Symbol | Address |
|---|---|
| USDC (native) | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 |
| USDC.e | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 |
| USDT | 0xc2132D05D31c914a87C6611C10748AEb04B58e8F |
| WETH | 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 |
| WMATIC / POL | 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270 |
Trails handles the bridge + vault deposit; the Zerion CLI handles funding the wallet beforehand and confirming the vault position afterward. Install with npm i -g zerion-cli.
# 1. Fund the wallet (shows EVM + Solana deposit addresses)
zerion wallet fund --wallet agent-bot
# 2. Confirm the source-chain balance is in place before quoting
zerion portfolio --wallet agent-bot
# 3. Run the Trails deposit (Widget / hooks / API — see sections below)
# 4. Verify the vault position appeared on Polygon (DeFi positions)
zerion portfolio --wallet agent-bot
zerion analyze agent-bot --chain polygon --positions defi
The Trails SDK runs against the same EVM address that zerion wallet list reports — pass that address as ownerAddress / userAddress and use the vault contract as destinationToAddress / to.recipient in any of the integration modes below.
Trails deposits are EXACT_INPUT — the user specifies the input amount and the vault receives the computed settled amount. Because the final amount isn't known at calldata encoding time, import and use TRAILS_ROUTER_PLACEHOLDER_AMOUNT from 0xtrails:
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
// Trails replaces this with the real settled amount at execution time
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, receiverAddress]
Do not use this placeholder for functions that read the token balance internally (e.g. deposit(address receiver) without an explicit amount arg).
Import from 0xtrails/widget. Components are self-contained and take apiKey directly.
import { Fund } from '0xtrails/widget'
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'
const erc4626Abi = [
{
name: 'deposit',
type: 'function',
inputs: [
{ name: 'assets', type: 'uint256' },
{ name: 'receiver', type: 'address' },
],
outputs: [{ name: 'shares', type: 'uint256' }],
},
] as const
function VaultDepositWidget({
vaultAddress,
userAddress,
}: {
vaultAddress: `0x${string}`
userAddress: `0x${string}`
}) {
const calldata = encodeFunctionData({
abi: erc4626Abi,
functionName: 'deposit',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, userAddress],
})
return (
<Fund
apiKey="YOUR_TRAILS_API_KEY"
to={{
recipient: vaultAddress,
currency: "USDC",
chain: "polygon",
calldata,
}}
onFundingSuccess={({ sessionId }) => {
console.log('Deposit complete:', sessionId)
}}
onFundingError={({ error }) => console.error(error)}
/>
)
}
For supported protocols (Aave, Morpho, Yearn, Compound), use the Earn component with composable actions — no manual ABI encoding needed:
import { Earn } from '0xtrails/widget'
function AaveDepositWidget() {
return (
<Earn
apiKey="YOUR_TRAILS_API_KEY"
onEarnSuccess={({ sessionId }) => {
console.log('Earned:', sessionId)
}}
/>
)
}
For a specific Polygon lending market, add a to config:
<Earn
apiKey="YOUR_TRAILS_API_KEY"
to={{
chain: "polygon",
currency: "USDC",
}}
onEarnSuccess={({ sessionId }) => console.log(sessionId)}
/>
Widget props reference:
| Prop | Type | Description |
|---|---|---|
apiKey | string | Trails API key (required) |
to.recipient | string | Vault contract address |
to.currency | string | Token symbol or address |
to.chain | string | number | Chain name, ID, or viem Chain |
to.calldata | string | ABI-encoded deposit call (use TRAILS_ROUTER_PLACEHOLDER_AMOUNT) |
to.amount | string | Fixed deposit amount (optional) |
paymentMethod | string | "CONNECTED_WALLET" (default), "CRYPTO_TRANSFER", "CREDIT_DEBIT_CARD", "EXCHANGE" |
Import hooks from 0xtrails. Hooks require TrailsProvider context.
import { TrailsProvider } from '0xtrails'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<TrailsProvider trailsApiKey="YOUR_TRAILS_API_KEY">
{children}
</TrailsProvider>
)
}
For supported protocols, use lend() from 0xtrails — Trails handles the contract calls internally:
import { useTrailsSendTransaction, lend, useEarnMarkets } from '0xtrails'
function AaveDepositButton({ amount }: { amount: string }) {
const { data: markets } = useEarnMarkets({ chain: 'polygon', provider: 'aave' })
const { sendTransaction, isPending } = useTrailsSendTransaction({
actions: [
lend({
marketId: 'polygon-usdc-aave-v3-lending', // use useEarnMarkets to discover IDs
amount,
}),
],
})
return (
<button onClick={() => sendTransaction()} disabled={isPending}>
{isPending ? 'Depositing...' : 'Deposit to Aave on Polygon'}
</button>
)
}
Use useEarnMarkets to discover available marketId values at runtime rather than hardcoding them:
import { useEarnMarkets } from '0xtrails'
function MarketPicker() {
const { data: markets } = useEarnMarkets({
chain: 'polygon',
type: 'lending', // optional: 'lending' | 'vault'
provider: 'aave', // optional: filter by protocol
sortBy: 'apy', // optional
})
return (
<ul>
{markets?.map(m => (
<li key={m.marketId}>{m.name} — {m.apy}% APY</li>
))}
</ul>
)
}
For protocols not natively supported by Trails, encode calldata manually:
import { useTrailsSendTransaction, TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'
const erc4626Abi = [
{
name: 'deposit',
type: 'function',
inputs: [
{ name: 'assets', type: 'uint256' },
{ name: 'receiver', type: 'address' },
],
outputs: [{ name: 'shares', type: 'uint256' }],
},
] as const
function useCustomVaultDeposit(vaultAddress: `0x${string}`, userAddress: `0x${string}`) {
const { sendTransaction, isPending } = useTrailsSendTransaction()
const deposit = () => {
const calldata = encodeFunctionData({
abi: erc4626Abi,
functionName: 'deposit',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, userAddress],
})
sendTransaction({
to: {
recipient: vaultAddress,
currency: "USDC",
chain: "polygon",
calldata,
},
})
}
return { deposit, isPending }
}
Full control over quote → commit → execute → wait. Use for server-side automation or non-React environments.
import { TrailsApi, TradeType } from '@0xtrails/api'
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'
const trailsApi = new TrailsApi('YOUR_TRAILS_API_KEY')
const erc4626Abi = [
{
name: 'deposit',
type: 'function',
inputs: [
{ name: 'assets', type: 'uint256' },
{ name: 'receiver', type: 'address' },
],
outputs: [{ name: 'shares', type: 'uint256' }],
},
] as const
async function depositToPolygonVault(params: {
userAddress: string
vaultAddress: string
depositTokenAddress: string // token the vault accepts on Polygon
originChainId: number
originTokenAddress: string
originTokenAmount: bigint // in source token's smallest unit
}) {
const { userAddress, vaultAddress, depositTokenAddress, originChainId, originTokenAddress, originTokenAmount } = params
// Encode vault deposit call — Trails replaces TRAILS_ROUTER_PLACEHOLDER_AMOUNT at execution
const destinationCallData = encodeFunctionData({
abi: erc4626Abi,
functionName: 'deposit',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, userAddress as `0x${string}`],
})
// 1. Quote — includes bridge + swap + vault deposit in one route
const { intent, gasFeeOptions } = await trailsApi.quoteIntent({
ownerAddress: userAddress,
originChainId,
originTokenAddress,
originTokenAmount,
destinationChainId: 137, // Polygon
destinationTokenAddress: depositTokenAddress,
destinationToAddress: vaultAddress,
destinationCallData, // capital D
tradeType: TradeType.EXACT_INPUT,
options: {
slippageTolerance: 0.005,
},
})
// 2. Commit — pass the full intent object; must execute within 10 minutes
const { intentId } = await trailsApi.commitIntent({ intent })
// 3. Execute — user signs the intent (gasless path)
await trailsApi.executeIntent({
intentId,
depositSignature: {
intentSignature: await signIntent(intent, walletClient), // EIP-712 sign
selectedGasFeeOption: gasFeeOptions.feeOptions[0],
userNonce: 1,
deadline: Math.floor(Date.now() / 1000) + 3600,
},
})
// Alternative: user submits deposit transaction manually
// await trailsApi.executeIntent({ intentId, depositTransactionHash: '0x...' })
// 4. Poll until bridge + vault deposit settles (can take 1-5 min)
let done = false
let intentReceipt
while (!done) {
;({ intentReceipt, done } = await trailsApi.waitIntentReceipt({ intentId }))
}
if (intentReceipt.status === 'SUCCEEDED') {
console.log('Vault deposit complete:', intentReceipt.destinationTransaction?.txnHash)
} else {
throw new Error(`Deposit failed: ${intentReceipt.originTransaction?.statusReason}`)
}
return intentReceipt
}
Note on signIntent: This is your wallet's EIP-712 signing function applied to the intent data. With viem, use signTypedData on the intent's structured data. Refer to the Trails execution docs for the exact typed data schema.
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'
const calldata = encodeFunctionData({
abi: [{
name: 'deposit',
type: 'function',
inputs: [
{ name: 'assets', type: 'uint256' },
{ name: 'receiver', type: 'address' },
],
outputs: [{ name: 'shares', type: 'uint256' }],
}] as const,
functionName: 'deposit',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, receiverAddress],
})
const calldata = encodeFunctionData({
abi: [{
name: 'deposit',
type: 'function',
inputs: [{ name: 'amount', type: 'uint256' }],
outputs: [],
}] as const,
functionName: 'deposit',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT],
})
const calldata = encodeFunctionData({
abi: [{
name: 'stake',
type: 'function',
inputs: [{ name: 'amount', type: 'uint256' }],
outputs: [],
}] as const,
functionName: 'stake',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT],
})
| Parameter | Type | Description |
|---|---|---|
ownerAddress | string | User's wallet address |
originChainId | number | Source chain ID |
originTokenAddress | string | Source token address |
originTokenAmount | bigint | Amount in smallest unit |
destinationChainId | number | 137 for Polygon |
destinationTokenAddress | string | Token the vault accepts |
destinationToAddress | string | Vault contract address |
destinationCallData | string | ABI-encoded vault call (capital D) |
tradeType | TradeType | TradeType.EXACT_INPUT for all deposit flows |
TRAILS_ROUTER_PLACEHOLDER_AMOUNT — do not use a raw BigInt('0xff...') constant.destinationToAddress / to.recipient is a trusted contract.decodeFunctionData from viem before using in production.0x3c49...) only, not USDC.e (0x2791...).| Code | Cause | Fix |
|---|---|---|
missing_api_key | API key not set | Check TRAILS_API_KEY or apiKey prop |
quote_failed | No route to vault token | Confirm vault's deposit token is Trails-supported |
quote_expired | >5 min since quote | Re-quote and commit immediately |
intent_expired | >10 min since commit | Re-quote, re-commit, then execute |
tx_reverted | Vault deposit call reverted | Verify ABI, token address, and placeholder usage |
slippage_exceeded | Price moved beyond tolerance | Increase slippageTolerance or retry |
unsupported_chain | Polygon not available | Call getChains() to verify |