Skip to main content
What you’ll build:
  • Tool functions for portfolio lookup, token search, and transaction history
  • A working agent using the OpenAI SDK that answers wallet questions
  • Patterns for connecting Zerion data to any AI framework
Time: ~15 minutes

Prerequisites

  • A Zerion API key (get one here)
  • Node.js 18+
  • An OpenAI API key (for the agent example — you can adapt to any LLM)

Steps

1
Define the Zerion tool functions
2
Create reusable functions that your agent can call. Each function maps to a Zerion API endpoint: wallet portfolio, wallet positions, fungible assets search, and wallet transactions.
3
const API_KEY = process.env.ZERION_API_KEY;
const BASE_URL = "https://api.zerion.io/v1";
const headers = {
  accept: "application/json",
  authorization: `Basic ${btoa(API_KEY + ":")}`,
};

// Tool 1: Get wallet portfolio summary
async function getWalletPortfolio(address) {
  const res = await fetch(
    `${BASE_URL}/wallets/${address}/portfolio?currency=usd`,
    { headers }
  );
  const { data } = await res.json();
  const attrs = data.attributes;
  return {
    total_value: attrs.total.positions,
    change_24h_percent: attrs.changes.percent_1d,
    change_24h_usd: attrs.changes.absolute_1d,
    chains: attrs.positions_distribution_by_chain,
  };
}

// Tool 2: Get top token positions
async function getWalletPositions(address, limit = 10) {
  const res = await fetch(
    `${BASE_URL}/wallets/${address}/positions/?currency=usd&sort=-value&filter[positions]=only_simple`,
    { headers }
  );
  const { data } = await res.json();
  return data.slice(0, limit).map((pos) => ({
    symbol: pos.attributes.fungible_info.symbol,
    name: pos.attributes.fungible_info.name,
    chain: pos.relationships.chain.data.id,
    quantity: pos.attributes.quantity.float,
    value_usd: pos.attributes.value,
    price: pos.attributes.price,
  }));
}

// Tool 3: Search for a token by name
async function searchToken(query) {
  const res = await fetch(
    `${BASE_URL}/fungibles/?currency=usd&filter[search_query]=${encodeURIComponent(query)}&sort=-market_data.market_cap&page[size]=5`,
    { headers }
  );
  const { data } = await res.json();
  return data.map((token) => ({
    id: token.id,
    symbol: token.attributes.symbol,
    name: token.attributes.name,
    price: token.attributes.market_data?.price,
    market_cap: token.attributes.market_data?.market_cap,
  }));
}

// Tool 4: Get recent transactions
async function getRecentTransactions(address, limit = 5) {
  const res = await fetch(
    `${BASE_URL}/wallets/${address}/transactions/?currency=usd&page[size]=${limit}`,
    { headers }
  );
  const { data } = await res.json();
  return data.map((tx) => ({
    type: tx.attributes.operation_type,
    timestamp: tx.attributes.mined_at,
    chain: tx.relationships.chain.data.id,
    transfers: tx.attributes.transfers?.map((t) => ({
      direction: t.direction,
      symbol: t.fungible_info?.symbol || "NFT",
      quantity: t.quantity?.float,
      value_usd: t.value,
    })),
  }));
}
4
Wire up the agent with tool definitions
5
Define the tools schema so the LLM knows what functions are available and what parameters they accept.
6
import OpenAI from "openai";

const openai = new OpenAI();

const tools = [
  {
    type: "function",
    function: {
      name: "getWalletPortfolio",
      description: "Get total portfolio value, 24h change, and chain breakdown for a wallet address",
      parameters: {
        type: "object",
        properties: {
          address: { type: "string", description: "Wallet address (0x... or ENS)" },
        },
        required: ["address"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "getWalletPositions",
      description: "Get the top token holdings for a wallet, sorted by USD value",
      parameters: {
        type: "object",
        properties: {
          address: { type: "string", description: "Wallet address" },
          limit: { type: "number", description: "Max tokens to return (default 10)" },
        },
        required: ["address"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "searchToken",
      description: "Search for a token by name or symbol and get its price and market cap",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Token name or symbol to search for" },
        },
        required: ["query"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "getRecentTransactions",
      description: "Get recent transactions for a wallet with transfer details",
      parameters: {
        type: "object",
        properties: {
          address: { type: "string", description: "Wallet address" },
          limit: { type: "number", description: "Max transactions to return (default 5)" },
        },
        required: ["address"],
      },
    },
  },
];

// Map function names to implementations
const toolFunctions = {
  getWalletPortfolio,
  getWalletPositions,
  searchToken,
  getRecentTransactions,
};
7
Run the agent loop
8
Process user messages, call tools when the LLM requests them, and return the results.
9
async function chat(userMessage) {
  const messages = [
    {
      role: "system",
      content: "You are a helpful onchain assistant. Use the available tools to look up wallet data, token prices, and transactions. Always cite specific numbers from the API.",
    },
    { role: "user", content: userMessage },
  ];

  while (true) {
    const response = await openai.chat.completions.create({
      model: "gpt-4o", // or any model that supports tool calling
      messages,
      tools,
    });

    const message = response.choices[0].message;
    messages.push(message);

    // If no tool calls, return the final answer
    if (!message.tool_calls?.length) {
      return message.content;
    }

    // Execute each tool call and append results
    for (const toolCall of message.tool_calls) {
      const fn = toolFunctions[toolCall.function.name];
      const args = JSON.parse(toolCall.function.arguments);
      const result = await fn(...Object.values(args));

      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        content: JSON.stringify(result),
      });
    }
  }
}

// Try it
const answer = await chat("What tokens does vitalik.eth hold? What's the total value?");
console.log(answer);
10
Full working example
11
Save as agent.mjs and run with node agent.mjs:
12
import OpenAI from "openai";

const ZERION_API_KEY = process.env.ZERION_API_KEY;
const BASE_URL = "https://api.zerion.io/v1";
const headers = {
  accept: "application/json",
  authorization: `Basic ${btoa(ZERION_API_KEY + ":")}`,
};

async function getWalletPortfolio(address) {
  const res = await fetch(`${BASE_URL}/wallets/${address}/portfolio?currency=usd`, { headers });
  const { data } = await res.json();
  const a = data.attributes;
  return { total_value: a.total.positions, change_24h_percent: a.changes.percent_1d, chains: a.positions_distribution_by_chain };
}

async function getWalletPositions(address, limit = 10) {
  const res = await fetch(`${BASE_URL}/wallets/${address}/positions/?currency=usd&sort=-value&filter[positions]=only_simple`, { headers });
  const { data } = await res.json();
  return data.slice(0, limit).map((p) => ({
    symbol: p.attributes.fungible_info.symbol, chain: p.relationships.chain.data.id,
    quantity: p.attributes.quantity.float, value_usd: p.attributes.value,
  }));
}

async function searchToken(query) {
  const res = await fetch(`${BASE_URL}/fungibles/?currency=usd&filter[search_query]=${encodeURIComponent(query)}&sort=-market_data.market_cap&page[size]=5`, { headers });
  const { data } = await res.json();
  return data.map((t) => ({ symbol: t.attributes.symbol, name: t.attributes.name, price: t.attributes.market_data?.price }));
}

async function getRecentTransactions(address, limit = 5) {
  const res = await fetch(`${BASE_URL}/wallets/${address}/transactions/?currency=usd&page[size]=${limit}`, { headers });
  const { data } = await res.json();
  return data.map((tx) => ({
    type: tx.attributes.operation_type, timestamp: tx.attributes.mined_at,
    chain: tx.relationships.chain.data.id,
    transfers: tx.attributes.transfers?.map((t) => ({ direction: t.direction, symbol: t.fungible_info?.symbol || "NFT", quantity: t.quantity?.float })),
  }));
}

const toolFunctions = { getWalletPortfolio, getWalletPositions, searchToken, getRecentTransactions };

const tools = [
  { type: "function", function: { name: "getWalletPortfolio", description: "Get portfolio value and chain breakdown", parameters: { type: "object", properties: { address: { type: "string" } }, required: ["address"] } } },
  { type: "function", function: { name: "getWalletPositions", description: "Get top token holdings by value", parameters: { type: "object", properties: { address: { type: "string" }, limit: { type: "number" } }, required: ["address"] } } },
  { type: "function", function: { name: "searchToken", description: "Search tokens by name/symbol", parameters: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } } },
  { type: "function", function: { name: "getRecentTransactions", description: "Get recent wallet transactions", parameters: { type: "object", properties: { address: { type: "string" }, limit: { type: "number" } }, required: ["address"] } } },
];

const openai = new OpenAI();

async function chat(userMessage) {
  const messages = [
    { role: "system", content: "You are a helpful onchain data assistant. Use tools to answer questions about wallets, tokens, and transactions." },
    { role: "user", content: userMessage },
  ];

  while (true) {
    const res = await openai.chat.completions.create({ model: "gpt-4o", messages, tools });
    const msg = res.choices[0].message;
    messages.push(msg);

    if (!msg.tool_calls?.length) return msg.content;

    for (const tc of msg.tool_calls) {
      const fn = toolFunctions[tc.function.name];
      const args = JSON.parse(tc.function.arguments);
      const result = await fn(...Object.values(args));
      messages.push({ role: "tool", tool_call_id: tc.id, content: JSON.stringify(result) });
    }
  }
}

const answer = await chat("What's in vitalik.eth's wallet? Give me a summary.");
console.log(answer);

Adapting to other frameworks

The tool functions work with any AI framework. Here’s how to connect them: Anthropic Claude (tool_use)
const response = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  messages: [{ role: "user", content: userMessage }],
  tools: tools.map((t) => ({
    name: t.function.name,
    description: t.function.description,
    input_schema: t.function.parameters,
  })),
});
LangChain
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const portfolioTool = tool(
  async ({ address }) => JSON.stringify(await getWalletPortfolio(address)),
  {
    name: "getWalletPortfolio",
    description: "Get portfolio value and chain breakdown",
    schema: z.object({ address: z.string() }),
  }
);
Vercel AI SDK
import { tool } from "ai";
import { z } from "zod";

const walletTool = tool({
  description: "Get portfolio value and chain breakdown",
  parameters: z.object({ address: z.string() }),
  execute: async ({ address }) => getWalletPortfolio(address),
});

Next steps

  • Connect via MCP to give AI tools access to the full Zerion docs and API spec
  • Add PnL tracking tools for cost basis and gain/loss analysis
  • Use webhooks to push new transactions to your agent in real time
  • Explore x402 for per-request stablecoin payments (no API keys needed)