Adding Payments to a LangChain Agent with Bithaven
A step-by-step tutorial for integrating USDC payments into a LangChain agent using Bithaven's MCP endpoints. Define custom tools, wire them into an agent executor, and let your LangChain agent check balances and send payments.
LangChain agents can search the web, query databases, run code, and orchestrate multi-step workflows. But they cannot spend money unless you give them the tools to do it. In this tutorial, you will connect a LangChain agent to a USDC wallet on Base using Bithaven, giving it the ability to check balances, send payments, and request human approval for large transactions.
We will use LangChain's @tool decorator to define Bithaven operations as standard tools, then wire them into a ReAct agent that can reason about when and how to use them.
Prerequisites
- A Bithaven account (sign up free)
- An agent wallet with an API key (created from the dashboard)
- USDC deposited to your master wallet on Base
- Python 3.11+ with
langchain,langchain-openai, andrequestsinstalled - An OpenAI API key (or swap in any LangChain-supported model)
Step 1: Install dependencies
pip install langchain langchain-openai requests
Step 2: Create the Bithaven client
We will start with a lightweight Python client that wraps Bithaven's MCP endpoints. Each method maps to one of the four core MCP tools.
import requests
from typing import Optional
class BithavenClient:
def __init__(self, api_key: str, base_url: str = "https://api.bithaven.ai"):
self.base_url = base_url
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
def check_balance(self) -> dict:
resp = requests.post(
f"{self.base_url}/mcp/v1/check-balance",
headers=self.headers,
)
resp.raise_for_status()
return resp.json()
def send_payment(
self, to: str, amount: str, memo: Optional[str] = None
) -> dict:
payload = {"to": to, "amount": amount}
if memo:
payload["memo"] = memo
resp = requests.post(
f"{self.base_url}/mcp/v1/send-payment",
headers=self.headers,
json=payload,
)
resp.raise_for_status()
return resp.json()
def get_tx_history(
self, page: int = 1, limit: int = 10
) -> dict:
resp = requests.post(
f"{self.base_url}/mcp/v1/get-tx-history",
headers=self.headers,
json={"page": page, "limit": limit},
)
resp.raise_for_status()
return resp.json()
def request_approval(
self, to: str, amount: str, reason: str
) -> dict:
resp = requests.post(
f"{self.base_url}/mcp/v1/request-approval",
headers=self.headers,
json={"to": to, "amount": amount, "reason": reason},
)
resp.raise_for_status()
return resp.json()
Step 3: Define LangChain tools
Using LangChain's @tool decorator, we turn each Bithaven method into a tool the agent can discover and call. The docstring is critical here because LangChain uses it to decide when to invoke each tool.
import os
import json
from langchain_core.tools import tool
bithaven = BithavenClient(api_key=os.environ["BITHAVEN_API_KEY"])
@tool
def check_balance() -> str:
"""Check the USDC balance of the agent wallet and the remaining
daily spend cap. Use this before making payments to confirm
sufficient funds are available."""
result = bithaven.check_balance()
return json.dumps(result, indent=2)
@tool
def send_payment(to: str, amount: str, memo: str = "") -> str:
"""Send a USDC payment to the specified wallet address. The
transaction is evaluated against the wallet's spending policies.
If the payment violates a policy, it will be blocked and an
error returned explaining which policy was violated.
Args:
to: The recipient wallet address (0x...)
amount: The USDC amount to send (e.g. '5.00')
memo: Optional description of what the payment is for
"""
result = bithaven.send_payment(to=to, amount=amount, memo=memo)
return json.dumps(result, indent=2)
@tool
def get_tx_history(page: int = 1, limit: int = 10) -> str:
"""Retrieve recent transaction history for the agent wallet.
Returns a list of past payments with amounts, recipients,
timestamps, and status.
Args:
page: Page number for pagination (default 1)
limit: Number of results per page (default 10)
"""
result = bithaven.get_tx_history(page=page, limit=limit)
return json.dumps(result, indent=2)
@tool
def request_approval(to: str, amount: str, reason: str) -> str:
"""Request human approval for a transaction that exceeds the
wallet's spending policies. Use this when a payment is blocked
due to policy limits and the agent believes the transaction
is still necessary.
Args:
to: The recipient wallet address
amount: The USDC amount requested
reason: A clear explanation of why this payment is needed
"""
result = bithaven.request_approval(
to=to, amount=amount, reason=reason
)
return json.dumps(result, indent=2)
Step 4: Create the agent
Now we wire the tools into a LangChain ReAct agent. The agent will reason about each task, decide which tools to use, and iterate until it has a final answer.
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
# Initialize the LLM
llm = ChatOpenAI(
model="gpt-4o",
temperature=0,
)
# Define the tools
tools = [check_balance, send_payment, get_tx_history, request_approval]
# System prompt that gives the agent financial context
prompt = ChatPromptTemplate.from_messages([
(
"system",
"You are a financial assistant with access to a USDC wallet "
"on Base via Bithaven. You can check your balance, send "
"payments, view transaction history, and request human "
"approval for large transactions. Always check your balance "
"before sending payments. If a payment is blocked by policy, "
"explain the violation and offer to request approval."
),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
# Create the agent
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
)
Step 5: Run it
# Check the wallet balance
result = agent_executor.invoke({
"input": "What is my current USDC balance?"
})
print(result["output"])
# Send a small payment
result = agent_executor.invoke({
"input": (
"Send 5 USDC to 0x1234...abcd for the DataAPI "
"monthly subscription."
)
})
print(result["output"])
# Handle a policy violation gracefully
result = agent_executor.invoke({
"input": (
"I need to pay 500 USDC to 0xabcd...1234 for a bulk "
"data export. If it exceeds my spending limit, please "
"request approval and explain why we need it."
)
})
print(result["output"])
When you run this with verbose=True, you will see the agent's reasoning at each step. For the 500 USDC payment, a typical trace looks like:
- Agent calls
check_balanceto see available funds and remaining daily cap - Agent calls
send_paymentwith the 500 USDC amount - Bithaven returns a policy violation (daily cap exceeded)
- Agent reads the error, then calls
request_approvalwith a clear justification - Agent reports back that approval has been requested and the human will be notified
Swapping models
Because we are using LangChain, you can swap the underlying LLM without changing any of the tool definitions. To use Claude instead of GPT-4o:
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(
model="claude-sonnet-4-20250514",
temperature=0,
)
To use a local model via Ollama:
from langchain_ollama import ChatOllama
llm = ChatOllama(
model="llama3.1",
temperature=0,
)
The tools, agent, and executor remain identical. This is one of the key benefits of using LangChain for agent finance: your financial integration is decoupled from your model choice.
Adding error handling for production
The examples above work for development, but production agents need proper error handling. Here is a hardened version of the send_payment tool:
@tool
def send_payment(to: str, amount: str, memo: str = "") -> str:
"""Send a USDC payment to the specified wallet address."""
try:
result = bithaven.send_payment(to=to, amount=amount, memo=memo)
return json.dumps(result, indent=2)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 403:
error_data = e.response.json()
return json.dumps({
"error": "Policy violation",
"details": error_data.get("message", "Transaction blocked by policy"),
"suggestion": "Use request_approval to escalate this transaction",
})
elif e.response.status_code == 400:
return json.dumps({
"error": "Invalid request",
"details": e.response.json().get("message", "Check parameters"),
})
else:
return json.dumps({"error": f"HTTP {e.response.status_code}"})
except requests.exceptions.ConnectionError:
return json.dumps({
"error": "Connection failed",
"details": "Could not reach the Bithaven API. Try again shortly.",
})
Returning structured JSON errors (instead of raising exceptions) lets the agent reason about failures and take appropriate action, like retrying, requesting approval, or informing the user.
Next steps
- Set up spending policies to define your agent's budget guardrails
- Configure approval workflows for human-in-the-loop oversight
- Read the MCP tool reference for all available operations
- Try the Claude integration if you are using Anthropic's SDK directly
Bithaven gives your AI agents a budget, not a blank check. Create your first agent wallet →
Ready to give your agents a budget?
Create an account and have your first agent transacting in minutes.
Request Access