Skip to main content

How to Set Spending Limits for LangChain Agents on Ethereum

· 2 min read
PolicyLayer Team
PolicyLayer

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:

  1. The Agent is oblivious: The LLM just tries to use the tool.
  2. 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.