How to Set Spending Limits for LangChain Agents on Ethereum
LangChain has become the standard for building reasoning loops, but out of the box, it lacks a secure wallet primitive. Most tutorials suggest passing a private key directly to a Tool. This is dangerous for production.
This guide shows you how to wrap a LangChain Tool with PolicyLayer to enforce hard spending limits.
The Risk of Raw Keys in Tools
When you define a Custom Tool in LangChain for blockchain interactions, it usually looks like this:
class SendEthTool extends Tool {
name = "send_eth";
description = "Sends ETH to an address";
async _call(input: string) {
// DANGER: No limits here!
const wallet = new Wallet(process.env.PRIVATE_KEY);
return wallet.sendTransaction(...);
}
}
If the LLM hallucinates the input amount or the recipient, the transaction executes immediately.
Step 1: Install the Policy Wallet SDK
Instead of using a raw ethers.Wallet or viem client, we use the PolicyWallet wrapper.
npm install @policylayer/sdk
Step 2: Create the Policy-Aware Tool
We modify the tool to use PolicyLayer's Two-Gate Enforcement.
import { PolicyWallet, createEthersAdapter } from '@policylayer/sdk';
import { Tool } from 'langchain/tools';
class SecureSendTool extends Tool {
name = "secure_send_eth";
description = "Safely sends ETH with spending limits";
private wallet: PolicyWallet;
constructor() {
super();
// Create adapter with your existing private key
const adapter = await createEthersAdapter(
process.env.PRIVATE_KEY,
process.env.RPC_URL
);
// Wrap with policy enforcement
this.wallet = new PolicyWallet(adapter, {
apiUrl: 'https://api.policylayer.com',
apiKey: process.env.POLICYLAYER_API_KEY
});
}
async _call(input: string) {
const { to, amount } = JSON.parse(input);
try {
// PolicyWallet.send() handles both gates automatically:
// 1. Validates intent against spending limits
// 2. Verifies fingerprint to prevent tampering
// 3. Signs and broadcasts only if approved
const result = await this.wallet.send({
chain: 'ethereum',
asset: 'eth',
to,
amount
});
return `Transaction Hash: ${result.hash}`;
} catch (error) {
return `BLOCKED: ${error.message}`;
}
}
}
Why This Matters
By wrapping the execution logic:
- The Agent is oblivious: The LLM just tries to use the tool.
- The Policy is sovereign: If the LLM tries to send 100 ETH,
wallet.validate()throws an error before a signature is ever generated.
This is the only way to safely deploy LangChain agents that handle real value on mainnet.
