How to give your AI agent a crypto wallet
Give the agent a tool that asks for a payment, and put the rules for that payment next to the key where the agent cannot reach them. Here is how to build that on Lit, with the code for the rule and the tool.
To give an AI agent a crypto wallet, give it a tool that asks for a payment, and keep the private key and the rules somewhere the agent cannot reach. On Lit Protocol the key lives inside a secure enclave. The rules are a JavaScript function, called a Lit Action, that runs in the same enclave before every signature. The agent gets an API key that can run that one function. It can ask for payments that follow your rules, and it cannot read the key or change the rules.
The reason to set it up this way is that an agent does what its context tells it to. If a web page or an email it reads contains a prompt injection, the agent may try to send money somewhere you did not intend. If the agent holds the private key, nothing stops it. If the rules live next to the key, the worst it can do is make a payment that passes the rules.
The rules
This action lets the agent pay vendors in USDC on Base. It only pays addresses on an allowlist, and never more than 20 USDC at a time.
// agent-pay.js
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const VENDORS = ["0xDataVendor...", "0xComputeVendor..."].map(a => a.toLowerCase());
const MAX = 20_000000; // 20 USDC, which has 6 decimals
async function main({ pkpId, to, amount }) {
if (!VENDORS.includes(to.toLowerCase())) return { paid: false, reason: "not an approved vendor" };
if (!(Number(amount) > 0 && Number(amount) <= MAX)) return { paid: false, reason: "amount must be between 0 and 20 USDC" };
const provider = new ethers.providers.StaticJsonRpcProvider("https://mainnet.base.org");
const wallet = new ethers.Wallet(await Lit.Actions.getPrivateKey({ pkpId }), provider);
const usdc = new ethers.Contract(USDC, ["function transfer(address,uint256) returns (bool)"], wallet);
const tx = await usdc.transfer(to, amount);
return { paid: true, hash: tx.hash };
}The reasons it returns go back to the agent, so it can tell the user why a payment did not happen.
The simplest total limit is the wallet's balance. Put in what the agent is allowed to spend this week and nothing more. If you want a daily cap that holds no matter how much is in the wallet, the action can read your own spending record before it signs. The post on writing signing policies as code shows that pattern.
Setting it up
Go to the Lit dashboard and click Create an account. Choose API key, fill in your email and an account name, and copy the account API key it gives you. Add some credit with Add Funds. Then create a wallet, put it and the action in a group, and make an API key for the agent that can only run that group.
LIT=https://api.chipotle.litprotocol.com/core/v1
WALLET=$(curl -s -X POST "$LIT/create_wallet" -H "X-Api-Key: $ACCOUNT_KEY" | jq -r .wallet_address)
echo $WALLET # the agent's wallet address
CID=$(jq -Rs . agent-pay.js | curl -s -X POST "$LIT/get_lit_action_ipfs_id" -H "Content-Type: application/json" -d @- | jq -r .)
GROUP=$(curl -s -X POST "$LIT/add_group" -H "X-Api-Key: $ACCOUNT_KEY" -H "Content-Type: application/json" \
-d '{"group_name":"agent","group_description":"","pkp_ids_permitted":[],"cid_hashes_permitted":[]}' | jq -r .group_id)
curl -s -X POST "$LIT/add_action_to_group" -H "X-Api-Key: $ACCOUNT_KEY" -H "Content-Type: application/json" \
-d "{\"group_id\":$GROUP,\"action_ipfs_cid\":\"$CID\"}"
curl -s -X POST "$LIT/add_pkp_to_group" -H "X-Api-Key: $ACCOUNT_KEY" -H "Content-Type: application/json" \
-d "{\"group_id\":$GROUP,\"pkp_id\":\"$WALLET\"}"
USAGE_KEY=$(curl -s -X POST "$LIT/add_usage_api_key" -H "X-Api-Key: $ACCOUNT_KEY" -H "Content-Type: application/json" \
-d '{"name":"agent","description":"","can_create_groups":false,"can_delete_groups":false,"can_create_pkps":false,
"manage_ipfs_ids_in_groups":[],"add_pkp_to_groups":[],"remove_pkp_from_groups":[],"execute_in_groups":['"$GROUP"']}' \
| jq -r .usage_api_key)Send USDC to the wallet address, plus a little ETH on Base for gas. New permissions can take up to 30 seconds to start working, so wait a minute before the agent's first payment, or retry it if it is refused.
The agent's tool
The agent sees a tool with a name, a description, and two inputs. When it calls the tool, your code sends the action and the inputs to Lit. The definition uses the tool format from the Anthropic API, with a run function that your code calls when the agent uses the tool. Other agent frameworks use almost the same shape.
import { readFileSync } from "node:fs";
const CODE = readFileSync("agent-pay.js", "utf8");
export const payVendor = {
name: "pay_vendor",
description: "Pay an approved vendor in USDC on Base. At most 20 USDC per payment.",
input_schema: {
type: "object",
properties: { to: { type: "string" }, amount_usdc: { type: "number" } },
required: ["to", "amount_usdc"],
},
async run({ to, amount_usdc }) {
const res = await fetch("https://api.chipotle.litprotocol.com/core/v1/lit_action", {
method: "POST",
headers: { "X-Api-Key": process.env.LIT_USAGE_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
code: CODE,
js_params: { pkpId: process.env.AGENT_WALLET, to, amount: String(Math.round(amount_usdc * 1e6)) },
}),
});
const out = await res.json();
if (!res.ok) return { paid: false, reason: typeof out === "string" ? out : out.message || `Lit returned ${res.status}` };
return out.response;
},
};Set LIT_USAGE_KEY to the usage key and AGENT_WALLET to the wallet address from the setup step. The limit in the description is only there to help the agent choose well. The limit that counts is the one in the action.
What the agent can and cannot do
With the usage key, the agent can run agent-pay.js against that one wallet. It cannot read the private key. It cannot add a new action, change the allowlist, or raise the limit, because those need your account key, and changing the code gives it a new hash that the agent's key is not allowed to run. To stop the agent, delete its usage key. It can take up to five minutes to stop working everywhere. The wallet and its money stay where they are.
The usage key is still a credential, so keep it out of the agent's transcript. Agent Keychain can store it and pass it to the tool's process without the agent seeing it. The same goes for any other API keys the agent needs. The post on giving Claude Code and MCP servers secrets walks through the setup.
Get started
To create an account, go to the Lit dashboard and click Create an account. The quickstart is at developer.litprotocol.com/quickstart, and Agent Keychain is documented at developer.litprotocol.com/keychain.