Build a trading agent on Velora in 10 minutes

Oct 18, 2026

Build a Velora trading agent in under 10 minutes

Product

You can build an AI agent that trades on-chain in about 10 minutes: Claude for reasoning, the Velora MCP server for quotes and orders, and a small wallet module that signs only within limits you set. It executes gasless swaps through Velora Delta, falls back to the Market API when needed, and needs no API key to start.

Asking an AI model for the price of ETH is easy. Letting it actually trade takes more: correct token addresses, the right execution path, approvals, signatures, and a way to stop it before it does something you didn't intend.

The Velora MCP server handles the first half of that. MCP (Model Context Protocol) is the open standard AI assistants such as Claude, Cursor and VS Code use to call external tools. Velora (formerly ParaSwap) runs one that gives any agent quotes, routing decisions and ready-to-sign orders for both execution paths, gasless Delta intents and Market swaps, across 10 chains, with no API key and no SDK to wire up. It never signs anything, so the second half stays with you: a small wallet module that signs only what you've allowed and asks you before it does.

In this guide you'll build both: a command-line agent you can tell "swap 25 USDC to ETH on Base" and watch it quote, explain, ask for your confirmation and execute.

What you'll build: an AI agent that swaps tokens on-chain

Architecture: you send a request to the agent (Claude), which calls the Velora MCP server for quotes, routing and order building, and local wallet tools for balances, approvals and signing with limits and a confirmation prompt.
  • Velora MCP server (https://ai.velora.xyz/mcp): 18 velora_* tools for quotes, Delta orders, Market transactions, order status, tokens and docs. Public, read-and-build only.

  • Wallet tools: five tools you write and run locally. Your private key never leaves your machine and never reaches the model.

  • Two execution paths:

    • Velora Delta: intent-based and gasless. The agent signs an order, solvers compete to fill it, and you get MEV (maximal extractable value) protection built in.

    • Market API: classic aggregator swap through Augustus v6.2. The agent sends a transaction and pays gas. Used when Delta isn't available for a pair or when selling native ETH.

Before you start

  • Node.js 20.12 or later (for the built-in .env loader)

  • An Anthropic API key

  • A fresh wallet used only for this agent, funded on Base with a small amount of USDC and a little ETH for gas (approval transactions and Market swaps need gas; Delta orders signed with a permit don't)

  • A Base RPC URL (the public https://mainnet.base.org works for testing)

Use a burner wallet. You're giving software the ability to sign for it.

Step 1 (minute 1): Connect the Velora MCP server to Claude

If you use Claude Code, add the server and talk to it directly:

claude mcp add --transport

Then ask:

Quote 25 USDC to ETH on Base. Which execution path would Velora use, and why?

Claude will call velora_get_quote and velora_explain_quote and tell you whether you'd get a Delta or a Market route. Configs for Cursor, VS Code and Windsurf are in the MCP installation guide.

That's an agent that can plan trades. Next, you'll give it a way to execute them.

Step 2 (minutes 2–3): Set up a TypeScript project

mkdir velora-agent && cd velora-agent
npm init -y && npm pkg set type=module
npm install --save-exact @anthropic-ai/claude-agent-sdk viem zod
npm install --save-dev

--save-exact pins versions. For anything that holds keys, pin your dependencies and commit the lockfile.

Create .env:

ANTHROPIC_API_KEY=sk-ant-...
PRIVATE_KEY=0x...        # burner wallet only
BASE_RPC_URL

Step 3 (minutes 3–7): Give the agent a wallet with spending limits

The Velora MCP server returns unsigned payloads: an EIP-712 order for Delta, or a transaction for Market. Your wallet module decides what gets signed. Every rule here is enforced in code, not in the prompt:

  • One chain (Base) and an allowlist of tokens, each with its address, decimals and a maximum amount per trade

  • Gasless EIP-2612 permits to the Delta contract for tokens that support them, such as USDC

  • Otherwise, approvals only to the two Velora spenders: the Delta contract and Augustus v6.2, for exact amounts

  • Transactions only to Augustus v6.2

  • Delta orders only if you're the owner and the amount is within limits

  • A y/N prompt in your terminal before anything is signed

The wallet's checks before signing: right chain (Base), a Velora contract (Delta or Augustus v6.2), the order owner is the agent wallet, the token is allowlisted and under its cap, and you type y. Any failed check returns REFUSED and the agent stops.

Create wallet.ts:

import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
import {
  createWalletClient, encodeAbiParameters, erc20Abi, formatUnits, http, parseAbi, parseAbiParameters,
  parseSignature, publicActions, serializeCompactSignature, signatureToCompactSignature, type Address, type Hex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import { createInterface } from "node:readline/promises";
import { z } from "zod";

process.loadEnvFile(); // reads .env

// Velora spenders (same address on every chain). Never interchangeable.
const DELTA: Address = "0x0000000000bbf5c5fd284e657f01bd000933c96d";
const AUGUSTUS = "0x6a000f20005980200259b80c5102003040001068"; // Augustus v6.2

// Native ETH, plus the tokens the agent may sell, the most it may sell per trade,
// and whether the token supports gasless EIP-2612 permits.
const ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const TOKENS: Record<string, { address: Address; decimals: number; max: bigint; permit: boolean }> = {
  USDC: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6, max: 100_000000n, permit: true },
  WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18, max: 50_000000000000000n, permit: false },
};
// Native ETH sold via Market is sent as tx value, with no approval to cap it: 0.02 ETH max.
const MAX_ETH = 20_000000000000000n;

const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const client = createWalletClient({ account, chain: base, transport: http(process.env.BASE_RPC_URL) }).extend(publicActions);

const permitAbi = parseAbi(["function version() view returns (string)", "function nonces(address) view returns (uint256)"]);

const is = (a: string, b: string) => a.toLowerCase() === b.toLowerCase();

function checkLimit(token: string, amount: bigint) {
  const [symbol, t] = Object.entries(TOKENS).find(([, t]) => is(t.address, token)) ?? [];
  if (!t) throw new Error(`${token} is not on the allowlist`);
  if (amount > t.max) throw new Error(`over the ${formatUnits(t.max, t.decimals)} ${symbol} limit`);
  return `${formatUnits(amount, t.decimals)} ${symbol}`;
}

async function confirm(question: string) {
  const rl = createInterface({ input: process.stdin, output: process.stdout });
  const answer = await rl.question(`\n⚠️  ${question}\n   Sign? (y/N) `);
  rl.close();
  if (answer.trim().toLowerCase() !== "y") throw new Error("user declined");
}

// Any thrown check becomes "REFUSED: <reason>" for the agent.
const guarded = (fn: (args: any) => Promise<unknown>) => async (args: any) => {
  try {
    const result = await fn(args);
    return { content: [{ type: "text" as const, text: JSON.stringify(result, (_, v) => (typeof v === "bigint" ? `${v}` : v)) }] };
  } catch (e) {
    return { content: [{ type: "text" as const, text: `REFUSED: ${(e as Error).message}` }], isError: true };
  }
};

export const walletServer = createSdkMcpServer({
  name: "wallet",
  tools: [
    tool("wallet_info", "Wallet address, current unix time, native ETH, and the allowlisted tokens with address, decimals, balance and allowances to Velora.",
      {},
      guarded(async () => ({
        address: account.address,
        now: Math.floor(Date.now() / 1000),
        ETH: { address: ETH, decimals: 18, balance: await client.getBalance({ address: account.address }) },
        tokens: await Promise.all(Object.entries(TOKENS).map(async ([symbol, { address, decimals, max, permit }]) => {
          const erc20 = { address, abi: erc20Abi } as const;
          return {
            symbol, address, decimals, maxPerTrade: max, permit,
            balance: await client.readContract({ ...erc20, functionName: "balanceOf", args: [account.address] }),
            allowanceDelta: await client.readContract({ ...erc20, functionName: "allowance", args: [account.address, DELTA] }),
            allowanceAugustus: await client.readContract({ ...erc20, functionName: "allowance", args: [account.address, AUGUSTUS] }),
          };
        })),
      }))),

    tool("sign_permit", "Sign a gasless EIP-2612 permit letting Velora Delta spend an exact amount of a token with permit: true. Pass the result as permit to velora_build_delta_order.",
      { token: z.string(), amount: z.string(), deadline: z.number() },
      guarded(async ({ token, amount, deadline }) => {
        const t = Object.values(TOKENS).find((t) => is(t.address, token));
        if (!t?.permit) throw new Error("token does not support permits, use approve_token");
        await confirm(`Permit: let Velora Delta spend ${checkLimit(token, BigInt(amount))} (no gas)`);
        const [name, version, nonce] = await Promise.all([
          client.readContract({ address: t.address, abi: erc20Abi, functionName: "name" }),
          client.readContract({ address: t.address, abi: permitAbi, functionName: "version" }),
          client.readContract({ address: t.address, abi: permitAbi, functionName: "nonces", args: [account.address] }),
        ]);
        const message = { owner: account.address, spender: DELTA, value: BigInt(amount), nonce, deadline: BigInt(deadline) };
        const signature = await account.signTypedData({
          domain: { name, version, chainId: base.id, verifyingContract: t.address },
          types: { Permit: [
            { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" },
            { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" },
          ] },
          primaryType: "Permit",
          message,
        });
        const { r, s, v, yParity } = parseSignature(signature);
        // Delta expects abi.encode(owner, spender, value, deadline, v, r, s): 224 bytes.
        return { permit: encodeAbiParameters(parseAbiParameters("address, address, uint256, uint256, uint8, bytes32, bytes32"),
          [message.owner, DELTA, message.value, message.deadline, Number(v ?? 27n + BigInt(yParity)), r, s]) };
      })),

    tool("approve_token", "Approve an exact amount of an allowlisted token to Velora Delta or Augustus v6.2.",
      { token: z.string(), spender: z.string(), amount: z.string() },
      guarded(async ({ token, spender, amount }) => {
        if (!is(spender, DELTA) && !is(spender, AUGUSTUS)) throw new Error("spender is not a Velora contract");
        await confirm(`Approve ${checkLimit(token, BigInt(amount))} to ${is(spender, DELTA) ? "Velora Delta" : "Augustus v6.2"}`);
        const hash = await client.writeContract({ address: token as Address, abi: erc20Abi, functionName: "approve", args: [spender as Address, BigInt(amount)] });
        await client.waitForTransactionReceipt({ hash });
        return { hash };
      })),

    tool("sign_delta_order", "Sign the toSign payload from velora_build_delta_order. Returns an ERC-2098 compact signature.",
      { toSign: z.object({ domain: z.any(), types: z.any(), value: z.any() }), summary: z.string() },
      guarded(async ({ toSign: { domain, types, value }, summary }) => {
        if (Number(domain.chainId) !== base.id || !is(domain.verifyingContract, DELTA)) throw new Error("not a Velora Delta order on Base");
        if (!is(value.owner, account.address)) throw new Error("order owner is not this wallet");
        await confirm(`Delta order: sell ${checkLimit(value.srcToken, BigInt(value.srcAmount))}. ${summary}`);
        const { EIP712Domain, ...orderTypes } = types;
        const signature = await account.signTypedData({ domain, types: orderTypes, primaryType: "Order", message: value });
        return { signature: serializeCompactSignature(signatureToCompactSignature(parseSignature(signature))) };
      })),

    tool("send_transaction", "Send a Market swap built by velora_build_transaction. Only Augustus v6.2 is allowed.",
      { to: z.string(), data: z.string(), value: z.string(), summary: z.string() },
      guarded(async ({ to, data, value, summary }) => {
        if (!is(to, AUGUSTUS)) throw new Error("destination is not Augustus v6.2");
        if (BigInt(value) > MAX_ETH) throw new Error("native ETH value over the limit");
        await confirm(`Market swap: ${summary}`);
        const hash = await client.sendTransaction({ to: AUGUSTUS, data: data as Hex, value: BigInt(value) });
        const { status } = await client.waitForTransactionReceipt({ hash });
        return { hash, status };
      })),
  ],
});

A few details worth knowing:

  • Permits come first. USDC on Base supports EIP-2612, so for a Delta trade the agent signs a permit for the exact amount instead of sending an approval transaction. The permit travels inside the Delta order, so the whole trade costs no gas. WETH doesn't support permits, and velora_build_transaction takes no permit, so those cases fall back to an exact approval.

  • Exact approvals cap your exposure. The Market path can't easily inspect swap calldata, so the approval is the real limit: the agent can only ever spend what you approved, for that one trade. Native ETH has no approval, which is why MAX_ETH caps the transaction value instead.

  • The agent only trades tokens in TOKENS. To support another token, add its address, decimals and per-trade max there.

  • Delta signatures are ERC-2098 compact. Velora expects the 64-byte form, which is what serializeCompactSignature produces.

Step 4 (minutes 7–9): Write the agent with the Claude Agent SDK

The agent is Claude, running through the Claude Agent SDK, with two MCP servers attached: Velora's (remote) and your wallet (in-process). The system prompt encodes Velora's agent rules: quote with mode=ALL, branch on the response type, pass routes verbatim, and show the numbers before asking for a signature.

Create agent.ts:

import { query } from "@anthropic-ai/claude-agent-sdk";
import { walletServer } from "./wallet.ts";

const task = process.argv[2] ?? "Swap 25 USDC to ETH on Base";

const systemPrompt = `You are a careful trading agent. You trade on Base (chainId 8453) through Velora.
Always pass partner: "my-trading-agent" to Velora tools.

Process for every trade:
1. Call wallet_info. It lists native ETH and the tokens this wallet can sell, with addresses,
   decimals, balances and allowances. Only trade between these tokens, and never invent
   addresses or decimals. Stop if the balance is too low.
2. Call velora_get_quote with mode "ALL", side "SELL", userAddress = wallet address.
   Selling native ETH: use mode "MARKET" instead.
3. Branch on result.responseType, never on the mode you asked for. Use velora_explain_quote
   if it is "market" so you can tell the user why Delta wasn't used.
4. Before signing, state: amount in, expected amount out, USD values, gas cost (Market only)
   and price impact. If price impact is above 2%, stop and explain.
5a. Delta: if allowanceDelta < amount and the token has permit: true, call sign_permit with
    amount = the quote's srcAmount and deadline = now + 600 (no gas). Otherwise approve_token
    to the Delta spender for the exact amount.
    velora_build_delta_order with route = result.raw.delta.route copied VERBATIM, owner = wallet,
    slippage 50, the same deadline, and permit = the sign_permit result (if any).
    Then sign_delta_order with result.raw.toSign,
    then velora_submit_delta_order with order = toSign.value and the signature.
    Poll velora_get_delta_order by id until COMPLETED, FAILED, EXPIRED, CANCELLED or REFUNDED.
5b. Market: if allowanceAugustus < amount (ERC-20 only), approve_token to Augustus v6.2
    (velora_build_transaction takes no permit).
    velora_build_transaction with priceRoute copied VERBATIM and its srcAmount/destAmount.
    Then send_transaction with to, data, value.
6. If a quote is older than ~30 seconds when you submit, requote.
7. Finish with a short receipt: path used, amounts, tx hash or order id.
If sign_permit, approve_token, sign_delta_order or send_transaction returns REFUSED, do not retry with
different parameters. Explain and stop.`;

// In-process MCP servers (the wallet) need streaming input, so the prompt is an async generator.
async function* prompt() {
  yield { type: "user" as const, message: { role: "user" as const, content: task }, parent_tool_use_id: null, session_id: "" };
}

try {
  for await (const msg of query({
    prompt: prompt(),
    options: {
      systemPrompt,
      mcpServers: { velora: { type: "http", url: "https://ai.velora.xyz/mcp" }, wallet: walletServer },
      tools: [],                                    // no shell, file or web tools
      allowedTools: ["mcp__velora", "mcp__wallet"], // every tool on these two servers
      disallowedTools: ["mcp__velora__velora_get_tokens"], // too large; wallet_info has the addresses
    },
  })) {
    if (msg.type !== "assistant") continue;
    for (const block of msg.message.content) {
      if (block.type === "text") console.log(block.text);
      if (block.type === "tool_use") console.log(`  → ${block.name.split("__").pop()}`);
    }
  }
} catch (e) {
  // Print one readable line instead of Node's stack trace.
  console.error(`\n✖ ${(e as Error).message.replace("Claude Code returned an error result: ", "")}`);
  process.exit(1);
}

Step 5 (minute 10): Run your first AI swap

npx tsx agent.ts "Swap 25 USDC to ETH on Base"

If it stops with "Credit balance is too low", add credits to the Anthropic account behind your API key in the Claude Console under Billing.

You'll see the agent work through the steps and stop at your terminal before anything is signed. A Delta run looks like this (trimmed, your numbers will differ):

  → wallet_info
  → velora_get_quote
Velora returned a Delta route: 25 USDC → ~0.0061 ETH (~$24.96), no gas for you to pay,
price impact under 0.1%. USDC supports permits, so I'll sign one instead of an approval transaction.
  → sign_permit
⚠️  Permit: let Velora Delta spend 25 USDC (no gas)
   Sign? (y/N) y
  → velora_build_delta_order
  → sign_delta_order
⚠️  Delta order: sell 25 USDC. 25 USDC → ~0.0061 ETH, 0.5% slippage, expires in 10 min
   Sign? (y/N) y
  → velora_submit_delta_order
  → velora_get_delta_order
  → velora_get_delta_order
Order COMPLETED. Sold 25 USDC for 0.00611 ETH via Velora Delta. Order id: 3f2c…

Try a few variations:

  • "Sell 0.005 ETH for USDC on Base": native ETH goes through the Market path, so you'll get a transaction to confirm instead of an order. (Delta can also sell native ETH with an extra deposit step, which this agent skips for simplicity.)

  • "Swap 500 USDC to ETH": over the 100 USDC limit. The agent sees the limit in wallet_info and stops, and the wallet would refuse to sign anyway.

  • "Sell my PEPE": not in the wallet's token list, so the agent stops before quoting.

Velora Delta vs Market API: how the agent picks an execution path

You don't hard-code the path. The agent asks for mode=ALL and Velora returns exactly one route:

Velora Delta vs Market API. Delta: the agent signs an EIP-712 order, the solver pays gas, MEV protection is built in, permit or approve the Delta contract. Market API: the agent signs a transaction and pays gas, used when Delta doesn't support the pair or size or when selling native ETH, approve Augustus v6.2.

With Delta, the agent signs an order and the solver pays gas. With the Market API, the agent sends a transaction and pays gas itself; Velora returns a Market route when Delta doesn't support the pair or size, or when you sell native ETH.

When the answer is Market, velora_explain_quote returns the fallbackReason, so the agent can tell you why. If you need MEV protection specifically, ask for mode=DELTA; Velora returns an error rather than silently falling back.

Next steps: TWAP, limit orders and cross-chain swaps

This agent does one trade per run, on one chain, with you approving each step. From here:

  • Run it on a schedule. Swap the CLI argument for a strategy ("buy $20 of ETH every Monday") and replace the terminal prompt with a policy: auto-approve under a threshold, ask on Telegram above it, as in the Telegram trading bot prompt.

  • Use TWAP and limit orders. The MCP server also exposes velora_build_twap_delta_order and submit_twap_order, and Delta orders take a limitAmount, for larger trades or price-sensitive ones. Two changes to the wallet: these orders settle over time, so they need an on-chain approval instead of a permit, and sign_delta_order must also accept the TWAP order type. See the TWAP API guide.

  • Go cross-chain. Pass destChainId to velora_get_quote and Delta handles the bridge (how crosschain swaps work, crosschain Delta docs). Add the destination chain's tokens to your allowlist.

  • Check plans before you run them. velora_validate_agent_plan flags common mistakes, such as using network instead of chainId or asking for MEV protection on a Market route.

Moving a trading agent to production

Every Velora endpoint is public. The partner value you pass identifies your traffic, and the free tier is rate-limited. When your agent, or the product you build around it, needs more throughput, create an account in Velora Portal to pick a Pro API plan, register your app, set where partner fees are paid, and track swaps and volume.

Before giving an agent real funds:

  • Test the agent with a handful of prompts, including ones it should refuse, and rerun them whenever you change the model or prompt (evals for trading agents will get their own article)

  • Keep the private key out of the model's reach, as here, and preferably in a hardware wallet, KMS or smart account with spending limits

  • Keep limits, allowlists and confirmations in code, not in the prompt

  • Pin exact dependency versions and commit your lockfile

  • Log every quote, signature and order id

FAQ

Do I need an API key to use the Velora MCP server?

No. The Velora MCP server and the Velora API are public. Pass a partner name to identify your traffic. For higher rate limits, pick a Pro API plan in Velora Portal.

Can the Velora MCP server sign or execute trades?

No. It returns unsigned payloads only: EIP-712 orders for Delta and transactions for the Market API. Signing happens in your own wallet code, so your private key never reaches the server or the model.

What is Velora Delta?

Velora Delta is an intent-based execution layer. You sign an order, solvers compete to fill it, and the solver pays gas. Orders are protected from MEV by design. How Delta works.

Which chains does the agent support?

Velora supports Ethereum, Optimism, BNB Chain, Gnosis, Unichain, Polygon, Base, Arbitrum, Avalanche and Robinhood Chain (chains and contracts). This guide uses Base. To trade elsewhere, change the chain and token addresses in wallet.ts and the chainId in the system prompt.

Does this work with AI models other than Claude?

Yes. Any MCP-compatible client or agent framework can use the Velora MCP server. The wallet guardrails in this guide are plain TypeScript and work with any agent loop.

Is it safe to let an AI agent trade crypto?

Only when limits are enforced outside the model. This guide uses a dedicated wallet, a token allowlist, per-trade caps, permits or exact approvals, and a human confirmation before every signature. Start with small amounts.

Resources

Related reading

Discover more about Velora

Developed by the team behind ParaSwap, Velora is the most comprehensive and flexible protocol for Decentralized Finance (DeFi). It uses decentralized intents to provide advanced features such as limit orders, super hooks, and chain-abstracted swaps.

2026 Velora® All Rights Reserved

Developed by the team behind ParaSwap, Velora is the most comprehensive and flexible protocol for Decentralized Finance (DeFi). It uses decentralized intents to provide advanced features such as limit orders, super hooks, and chain-abstracted swaps.

2026 Velora® All Rights Reserved

Developed by the team behind ParaSwap, Velora is the most comprehensive and flexible protocol for Decentralized Finance (DeFi). It uses decentralized intents to provide advanced features such as limit orders, super hooks, and chain-abstracted swaps.

2026 Velora® All Rights Reserved