Back to Blog
AI AgentsBlockchainTutorial

How to Build AI Agents with Blockchain Integration (2026 Guide)

May 20, 2026
11 min read

Most AI agent tutorials stop at "the agent calls an API and returns a result." That part is easy. The harder question is what happens when the agent needs to touch a blockchain.

An AI agent is a language model with tools it can call. A blockchain is a shared ledger that records transactions. I don't mean reading a price from a data feed. I mean reading contract state, building transactions, and, under the right conditions with the right guards, signing and sending them.

This guide is about building that. Real code, plain architecture, and the safety lines you draw before any of it touches real money. It assumes you know what an AI agent is, and you have at least looked at ethers.js or viem.

Why Blockchain + AI Agent Is Interesting

Each side covers a gap the other has.

A language model reads plain instructions but can't check what is true on-chain (the data stored on the blockchain). A smart contract enforces rules but can't reason about a vague request. Put them together and you get things like:

  • A DeFi portfolio manager that reads your positions, checks the market, and rebalances by a strategy you wrote in plain English
  • A DAO governance agent that watches proposals, sums them up, and votes by the rules the DAO set, with a human approval step before any transaction fires
  • A contract monitoring agent that watches on-chain events, reads them in context, and acts or sends an alert
  • An onboarding agent that walks a user through wallet setup, faucet claims, and a first transaction on a new protocol

They share one need. The agent needs to read on-chain state for context, and sometimes write to the chain to act.

The Architecture

An AI agent with blockchain integration has four layers. Keep them apart and the system stays testable, auditable, and easy to recover.

┌─────────────────────────────────────┐
│           Language Model Layer      │  ← reasoning, tool selection
├─────────────────────────────────────┤
│           Tool Layer                │  ← blockchain read/write functions
├─────────────────────────────────────┤
│           Wallet Layer              │  ← key management, signing
├─────────────────────────────────────┤
│           Chain Layer               │  ← RPC providers, contract ABIs
└─────────────────────────────────────┘

The model never touches keys. The wallet layer never makes reasoning calls. The chain layer knows nothing about the agent. It just takes calls. That split is what keeps the system safe to grow and safe to audit.

Setting Up the Chain Layer

Use viem or ethers.js v6. This is your line to the chain: reading contract state, estimating gas, building transaction objects.

// chain.ts
import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { mainnet } from 'viem/chains';

export const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(process.env.ETH_RPC_URL),
});

// Read a token balance, no signing required
export async function getTokenBalance(tokenAddress: `0x${string}`, accountAddress: `0x${string}`): Promise<bigint> {
  return publicClient.readContract({
    address: tokenAddress,
    abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
    functionName: 'balanceOf',
    args: [accountAddress],
  });
}

This function is plain infrastructure. No agent logic, no keys. The agent calls it. The agent does not own it.

Setting Up the Wallet Layer

This is the most sensitive part. A wallet here holds the private key, the secret that controls the funds. Or it hands off to a hardware module, a multi-sig, or a managed key service like Privy or Web3Auth. It exposes one thing: a signing function. Signing means the wallet uses the private key to approve a transaction so the chain accepts it.

// wallet.ts
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { mainnet } from 'viem/chains';

// In production: use a secrets manager, not a .env file
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);

export const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http(process.env.ETH_RPC_URL),
});

// The agent calls this, it doesn't call walletClient directly
export async function signAndSend(request: {
  to: `0x${string}`;
  data: `0x${string}`;
  value?: bigint;
}): Promise<`0x${string}`> {
  return walletClient.sendTransaction(request);
}

In production, the private key should live in a hardware security module (HSM) or a managed key service, not a process environment variable. The interface stays the same either way. signAndSend(request) does not care where the key lives.

Building the Tool Layer

This is what connects the language model to the chain. Each tool is a function with a clear description, typed parameters, and a return value the model can reason about.

Here it is with the Vercel AI SDK. The pattern works with LangChain, CrewAI, or any tool-calling framework:

import { tool } from 'ai';
import { z } from 'zod';
import { getTokenBalance, publicClient } from './chain';
import { signAndSend } from './wallet';
import { parseAbi, encodeFunctionData, formatUnits } from 'viem';

export const blockchainTools = {
  getBalance: tool({
    description: 'Get the ERC-20 token balance of an address. Returns the balance as a human-readable number.',
    parameters: z.object({
      tokenAddress: z.string().describe('The ERC-20 contract address'),
      accountAddress: z.string().describe('The wallet address to check'),
    }),
    execute: async ({ tokenAddress, accountAddress }) => {
      const balance = await getTokenBalance(tokenAddress as `0x${string}`, accountAddress as `0x${string}`);
      // Return formatted value so the model can reason about it naturally
      return { balance: formatUnits(balance, 18), raw: balance.toString() };
    },
  }),

  transferTokens: tool({
    description: 'Transfer ERC-20 tokens to another address. Requires human approval before executing.',
    parameters: z.object({
      tokenAddress: z.string().describe('The ERC-20 contract address'),
      to: z.string().describe('Recipient wallet address'),
      amount: z.string().describe('Amount in token units (not wei)'),
    }),
    execute: async ({ tokenAddress, to, amount }) => {
      // IMPORTANT: this is where the approval gate lives (see safety section)
      const data = encodeFunctionData({
        abi: parseAbi(['function transfer(address to, uint256 amount) returns (bool)']),
        functionName: 'transfer',
        args: [to as `0x${string}`, BigInt(amount) * BigInt(10 ** 18)],
      });
      const hash = await signAndSend({ to: tokenAddress as `0x${string}`, data });
      return { transactionHash: hash };
    },
  }),
};

The Language Model Layer

Now wire the tools to a model. The model picks when to call which tool, based on the user's request or its own reasoning loop.

import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { blockchainTools } from './tools';

export async function runAgent(userPrompt: string): Promise<string> {
  const { text } = await generateText({
    model: openai('gpt-4o'),
    tools: blockchainTools,
    maxSteps: 5, // prevent infinite tool-calling loops
    system: `You are a DeFi portfolio assistant. You can read blockchain state and, with user confirmation, execute transactions. Always explain what you're about to do before doing it. Never transfer more than the user explicitly asks for.`,
    prompt: userPrompt,
  });

  return text;
}

maxSteps matters. An agent with no step limit can loop forever when a tool call fails and it keeps retrying. Five to ten steps covers most tasks. If you need more, that is a sign to split the task into smaller sub-agents.

Safety Boundaries: The Non-Negotiable Part

An AI agent with a funded wallet and no safety layer is a bad idea. Full stop. Before any of this touches mainnet, build these in.

Human-in-the-loop for write operations

Reads are fine to automate. Writes are not. A write is anything that submits a transaction, and it should need a clear human yes, except in a few narrow, well-defined cases.

The pattern: the tool builds a transaction request, logs it for review, and waits for approval before it calls signAndSend. This can be a CLI prompt, a Slack message with approve and reject buttons, or a full UI. It just has to exist.

async function requireApproval(description: string): Promise<boolean> {
  // In production: send to Slack, trigger a UI prompt, etc.
  // In development: a simple readline works fine
  const readline = await import('readline');
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  return new Promise(resolve => {
    rl.question(`Approve transaction: ${description}? (yes/no) `, answer => {
      rl.close();
      resolve(answer.toLowerCase() === 'yes');
    });
  });
}

Spending limits

Set a cap per transaction and a cap per day, at the wallet level. If the agent tries to send above the cap, it fails before signing. No exceptions.

const MAX_TRANSACTION_VALUE_ETH = 0.1; // adjust per your risk tolerance
const MAX_DAILY_SPEND_ETH = 0.5;

// Track daily spend in a simple store (Redis or a DB in production)
async function checkSpendLimit(valueEth: number): Promise<void> {
  const todaySpend = await getDailySpend();
  if (valueEth > MAX_TRANSACTION_VALUE_ETH) {
    throw new Error(`Transaction value ${valueEth} ETH exceeds per-transaction limit`);
  }
  if (todaySpend + valueEth > MAX_DAILY_SPEND_ETH) {
    throw new Error(`Daily spend limit reached`);
  }
}

Read-only mode for most agents

Most agents don't need write access at all. They read chain state, sum it up, and lay out options for a human who then decides. Start there. Add write access only when you have a clear, audited reason for it.

Separate keys per environment

The testnet wallet and the mainnet wallet have nothing to do with each other. Never reuse keys across environments. The testnet wallet can have loose limits and no approval gate. The mainnet wallet has tight limits and needs approval for anything over a threshold.

A Complete Minimal Example

Putting it together: an agent that checks a token balance and, when asked, transfers tokens after the user approves.

// index.ts
import { runAgent } from './agent';

async function main() {
  const result = await runAgent(
    'Check the USDC balance of 0xYourAddress, then transfer 10 USDC to 0xRecipient if the balance is sufficient.'
  );
  console.log(result);
}

main().catch(console.error);

The agent will:

  1. Call getBalance for USDC on the given address
  2. Compare to the requested amount
  3. If sufficient, call transferTokens, which triggers the approval gate
  4. Wait for human confirmation
  5. Submit the transaction and return the hash

That is the full loop. Reason, read, reason again, propose the action, get approval, execute.

What Breaks in Production

A few things catch most builders off guard.

RPC rate limits. Free RPC endpoints (Infura free tier, Alchemy free tier) throttle hard. An agent that hits a chain tool several times per reasoning step runs into the limit fast. Use a paid plan or run your own node for anything in production.

Gas estimation failures. If estimateGas fails before the transaction, the agent may read it as a tool error and try another path, or loop. Handle the error and return a clear message to the model.

Nonce management. If the agent sends several transactions in quick succession, nonce conflicts cause failures. Use a nonce manager or queue transactions one at a time.

Stale on-chain data. Public RPC nodes can run a little behind the chain head. If your agent reads a value and acts on it right away, the state may have moved. Confirm with a second read after the transaction confirms.

Where to Go From Here

The architecture above handles single-agent tasks. For bigger workflows, like portfolio rebalancing across protocols, multi-step DeFi strategies, or agents that work together, you need multi-agent orchestration.

The building blocks are the same: isolated tool layers, clear approval gates, spending limits. The new piece is routing. Which agent owns which decision, and how they hand off context without the model losing the thread.

That is a longer post. So here is the real question: does your agent prepare the transaction and stop, or does it sign? Keep that line clear and you can sleep at night.

If you're already building at that layer, reach out. It's the kind of architecture problem I spend a lot of time on.


If you want to skip the boilerplate and get to the interesting parts faster, I do AI agent development consulting. The first call is free.