Building AI Agents That Interact With Blockchain: A Deep Technical Guide Using LangChain A developer built a production-grade AI agent that interacts with blockchain using LangChain's agent framework, ethers.js, and custom tools. The agent can read on-chain data, interact with smart contracts, and execute DeFi operations. The guide provides step-by-step instructions for creating tools to fetch ETH balances, ERC-20 token balances, and call read-only contract functions. Most tutorials on AI agents stop at chat interfaces and RAG pipelines. This one doesn't. This guide walks through building a production-grade AI agent that can read on-chain data, interact with smart contracts, and execute DeFi operations — using LangChain's agent framework, ethers.js, and a set of custom tools you'll write from scratch. By the end, you'll have an agent that can: mkdir ai-blockchain-agent && cd ai-blockchain-agent npm init -y npm install langchain @langchain/openai @langchain/core ethers dotenv npm install -D typescript ts-node @types/node hardhat npx tsc --init .env: OPENAI API KEY=your openai key ALCHEMY RPC URL=https://eth-mainnet.g.alchemy.com/v2/your key PRIVATE KEY=your wallet private key js // src/provider.ts import { ethers } from "ethers"; import as dotenv from "dotenv"; dotenv.config ; export const provider = new ethers.JsonRpcProvider process.env.ALCHEMY RPC URL ; export const signer = new ethers.Wallet process.env.PRIVATE KEY , provider ; LangChain agents are only as powerful as their tools. Every on-chain operation becomes a tool the agent can call. Each tool needs a name, description the LLM reads this to decide when to use it , and a func. js // src/tools/getBalance.ts import { DynamicTool } from "@langchain/core/tools"; import { provider } from "../provider"; import { ethers } from "ethers"; export const getBalanceTool = new DynamicTool { name: "get eth balance", description: "Get the ETH balance of a wallet address. Input should be a valid Ethereum address.", func: async address: string = { try { const balance = await provider.getBalance address ; return Balance: ${ethers.formatEther balance } ETH ; } catch err { return Error fetching balance: ${err} ; } }, } ; js // src/tools/getTokenBalance.ts import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; import { ethers } from "ethers"; import { provider } from "../provider"; const ERC20 ABI = "function balanceOf address owner view returns uint256 ", "function decimals view returns uint8 ", "function symbol view returns string ", ; export const getTokenBalanceTool = new DynamicStructuredTool { name: "get token balance", description: "Get the ERC-20 token balance for a wallet. Provide the token contract address and wallet address.", schema: z.object { tokenAddress: z.string .describe "ERC-20 token contract address" , walletAddress: z.string .describe "Wallet address to check" , } , func: async { tokenAddress, walletAddress } = { try { const contract = new ethers.Contract tokenAddress, ERC20 ABI, provider ; const balance, decimals, symbol = await Promise.all contract.balanceOf walletAddress , contract.decimals , contract.symbol , ; const formatted = ethers.formatUnits balance, decimals ; return ${formatted} ${symbol} ; } catch err { return Error: ${err} ; } }, } ; This is where it gets powerful — the agent can query any contract if you give it an ABI. js // src/tools/readContract.ts import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; import { ethers } from "ethers"; import { provider } from "../provider"; export const readContractTool = new DynamicStructuredTool { name: "read contract", description: "Call a read-only function on any smart contract. Provide the contract address, ABI as a JSON string, function name, and arguments array.", schema: z.object { contractAddress: z.string , abi: z.string .describe "JSON string of the contract ABI" , functionName: z.string , args: z.array z.string .default , } , func: async { contractAddress, abi, functionName, args } = { try { const parsedAbi = JSON.parse abi ; const contract = new ethers.Contract contractAddress, parsedAbi, provider ; const result = await contract functionName ...args ; return Result: ${result.toString } ; } catch err { return Error: ${err} ; } }, } ; js // src/tools/getGasPrice.ts import { DynamicTool } from "@langchain/core/tools"; import { provider } from "../provider"; import { ethers } from "ethers"; export const getGasPriceTool = new DynamicTool { name: "get gas price", description: "Get the current gas price on Ethereum mainnet in Gwei.", func: async = { try { const feeData = await provider.getFeeData ; const gasPriceGwei = ethers.formatUnits feeData.gasPrice , "gwei" ; const maxFeeGwei = ethers.formatUnits feeData.maxFeePerGas , "gwei" ; return Base gas price: ${parseFloat gasPriceGwei .toFixed 2 } Gwei | Max fee: ${parseFloat maxFeeGwei .toFixed 2 } Gwei ; } catch err { return Error: ${err} ; } }, } ; js // src/tools/getSwapQuote.ts import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; import { ethers } from "ethers"; import { provider } from "../provider"; // Uniswap V3 Quoter contract on mainnet const QUOTER ADDRESS = "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6"; const QUOTER ABI = "function quoteExactInputSingle address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96 external returns uint256 amountOut ", ; export const getSwapQuoteTool = new DynamicStructuredTool { name: "get swap quote", description: "Get a Uniswap V3 swap quote. Returns how many tokens you'd receive for a given input amount.", schema: z.object { tokenIn: z.string .describe "Input token contract address" , tokenOut: z.string .describe "Output token contract address" , fee: z.number .describe "Pool fee tier: 500, 3000, or 10000" , amountIn: z.string .describe "Amount in human-readable, e.g. '1.5' " , decimalsIn: z.number .default 18 , decimalsOut: z.number .default 18 , } , func: async { tokenIn, tokenOut, fee, amountIn, decimalsIn, decimalsOut } = { try { const quoter = new ethers.Contract QUOTER ADDRESS, QUOTER ABI, provider ; const amountInWei = ethers.parseUnits amountIn, decimalsIn ; const amountOut = await quoter.quoteExactInputSingle.staticCall tokenIn, tokenOut, fee, amountInWei, 0 ; const formatted = ethers.formatUnits amountOut, decimalsOut ; return You would receive approximately ${formatted} tokens for ${amountIn} input tokens. ; } catch err { return Error getting quote: ${err} ; } }, } ; Now plug all tools into a LangChain agent with memory and a system prompt that defines its behavior. js // src/agent.ts import { ChatOpenAI } from "@langchain/openai"; import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; import { BufferMemory } from "langchain/memory"; import { getBalanceTool } from "./tools/getBalance"; import { getTokenBalanceTool } from "./tools/getTokenBalance"; import { readContractTool } from "./tools/readContract"; import { getGasPriceTool } from "./tools/getGasPrice"; import { getSwapQuoteTool } from "./tools/getSwapQuote"; const tools = getBalanceTool, getTokenBalanceTool, readContractTool, getGasPriceTool, getSwapQuoteTool, ; const llm = new ChatOpenAI { modelName: "gpt-4o", temperature: 0, openAIApiKey: process.env.OPENAI API KEY, } ; const prompt = ChatPromptTemplate.fromMessages "system", You are a DeFi-native AI agent with direct access to the Ethereum blockchain. You can read wallet balances, token holdings, smart contract state, gas prices, and DEX swap quotes. Always verify addresses before acting. When providing numbers, format them clearly with appropriate units. Never guess on-chain data — always use your tools to fetch it. If a user asks you to perform a transaction, confirm all parameters before proceeding. , , new MessagesPlaceholder "chat history" , "human", "{input}" , new MessagesPlaceholder "agent scratchpad" , ; const memory = new BufferMemory { memoryKey: "chat history", returnMessages: true, } ; export async function createBlockchainAgent { const agent = await createOpenAIFunctionsAgent { llm, tools, prompt } ; return new AgentExecutor { agent, tools, memory, verbose: true, maxIterations: 10, } ; } js // src/index.ts import { createBlockchainAgent } from "./agent"; import as dotenv from "dotenv"; dotenv.config ; async function main { const agent = await createBlockchainAgent ; const queries = "What is the ETH balance of 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045?", "What's the current gas price on Ethereum?", "How much USDC would I get if I swap 1 ETH on Uniswap V3? USDC address is 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, WETH is 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, use the 500 fee tier.", ; for const query of queries { console.log \n🧠 Query: ${query} ; const result = await agent.invoke { input: query } ; console.log āœ… Response: ${result.output} ; } } main .catch console.error ; Run it: npx ts-node src/index.ts Never test write operations on mainnet directly. Fork it locally: npm install -D hardhat @nomicfoundation/hardhat-toolbox npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/your key Then in .env, point your ALCHEMY RPC URL to http://127.0.0.1:8545 http://127.0.0.1:8545 for local testing. Your agent will interact with a real state snapshot without risking real funds. This agent is a foundation. Production extensions include: The gap between AI agents and blockchain infrastructure is closing fast. LangChain's tool abstraction makes it straightforward to expose any on-chain operation as a callable function — the agent handles reasoning, sequencing, and decision-making on top. The architecture above is minimal by design. The real complexity in production systems isn't wiring the tools — it's designing safe execution boundaries, handling RPC failures gracefully, and making sure the agent can't be prompted into signing something it shouldn't. Build the guardrails before you build the features. I'm Fahad Arif — Blockchain Developer , Smart Contract Auditor, and DeFi Consultant. I build and secure production Web3 systems across EVM chains and Solana. More at my website.