- 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
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
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.
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,
})),
}));
}
Define the tools schema so the LLM knows what functions are available and what parameters they accept.
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,
};
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);
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)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)