Conversational Token Assistant
Build a chatbot that lets users query, transfer, and manage their tokens through natural language.
What You'll Build
Most users don't want to learn API endpoints, they want to talk to their tokens. In this tutorial you'll build a conversational assistant that can look up tokens, check balances, transfer objects, and answer questions about a user's collection, all through chat. We'll use tool-calling (function calling) so the LLM can invoke Dual APIs on behalf of the user.
Step 1, Define the Tool Schema
Create the tool definitions that tell the LLM what Dual operations are available:
const tools = [{type: 'function',function: {name: 'list_my_tokens',description: 'List tokens owned by the current user, optionally filtered by template',parameters: {type: 'object',properties: {template_name: {type: 'string',description: 'Filter by template name (optional)'},limit: {type: 'number',description: 'Max results (default 10)'}}}}},{type: 'function',function: {name: 'get_token_details',description: 'Get full details of a specific token by ID',parameters: {type: 'object',properties: {object_id: { type: 'string', description: 'The token/object ID' }},required: ['object_id']}}},{type: 'function',function: {name: 'transfer_token',description: 'Transfer a token to another wallet',parameters: {type: 'object',properties: {object_id: { type: 'string', description: 'Token to transfer' },recipient: { type: 'string', description: 'Recipient wallet or email' }},required: ['object_id', 'recipient']}}},{type: 'function',function: {name: 'search_tokens',description: 'Search tokens by properties like points, tier, or tags',parameters: {type: 'object',properties: {query: { type: 'string', description: 'Natural language search query' },filters: { type: 'object', description: 'Property filters' }},required: ['query']}}}];
Step 2, Implement Tool Handlers
Each tool maps to a Dual API call:
const API = 'https://api-testnet.dual.network';const toolHandlers = {async list_my_tokens({ template_name, limit = 10 }) {const params = new URLSearchParams({ limit });if (template_name) params.set('template_name', template_name);const res = await dualFetch(\"/objects?\" + params + "\");return res.results.map(o => ({id: o.id, name: o.template_name,properties: o.properties}));},async get_token_details({ object_id }) {return dualFetch(\"/objects/\" + object_id + "\");},async transfer_token({ object_id, recipient }) {return dualFetch('/ebus/actions', {method: 'POST',body: JSON.stringify({action_name: 'Transfer',object_id, new_owner: recipient})});},async search_tokens({ query, filters }) {return dualFetch('/objects/search', {method: 'POST',body: JSON.stringify({ filters: filters || {}, limit: 20 })});}};
Step 3, Build the Chat Loop
Create the conversation loop that sends messages, handles tool calls, and streams responses:
import Anthropic from '@anthropic-ai/sdk';const anthropic = new Anthropic();async function chat(userMessage, history = []) {history.push({ role: 'user', content: userMessage });const response = await anthropic.messages.create({model: 'claude-sonnet-4-5-20250514',max_tokens: 1024,system: \"You are a helpful assistant for managing DUAL tokens.The user may ask about their tokens, request transfers, or searchtheir collection. Use the available tools to fulfill requests.Always confirm before executing transfers.\",tools,messages: history});// Handle tool useif (response.stop_reason === 'tool_use') {const toolUse = response.content.find(b => b.type === 'tool_use');const result = await toolHandlers[toolUse.name](toolUse.input);history.push({ role: 'assistant', content: response.content });history.push({role: 'user',content: [{type: 'tool_result',tool_use_id: toolUse.id,content: JSON.stringify(result)}]});return chat('', history); // Continue the conversation}return response.content[0].text;}
Step 4, Example Conversations
Here's what the assistant can handle:
User: What tokens do I have?Bot: [calls list_my_tokens] You have 12 tokens across 3 templates:- 5x Coffee Stamp Cards (2 fully stamped)- 4x Event Passes (upcoming concert, 2 expired)- 3x Loyalty Badges (gold tier)User: Transfer my gold badge to alice@example.comBot: I can transfer your Gold Loyalty Badge (ID: obj_789) toalice@example.com. This action cannot be undone. Proceed?User: YesBot: [calls transfer_token] Done! The Gold Loyalty Badge has beentransferred to alice@example.com.
Safety Pattern: Always confirm destructive or irreversible actions (transfers, burns, deletes) before executing them. The assistant should summarize what it's about to do and wait for explicit confirmation.
Companion Repo: Get the full working source code for this tutorial at github.com/orgs/DualOrg/dual-ai-chatbot, clone it, add your API keys, and run it locally in minutes.
What's Next?
Add intelligence to your workflows with AI-Powered Webhook Automation, let AI decide how to handle incoming events.