Give your AI agent a Bitcoin wallet
Create a Lightning wallet for an AI agent, connect it over MCP or an SDK, get paid, pay other agents, and pay L402 APIs within limits you set.
By the end of this guide an agent has its own ln.bot wallet and Lightning address. It can take payments, tell when they arrive, pay other agents and pay APIs that charge per request over L402. Each step shows the CLI or the SDK call, in TypeScript and Python, and the last section covers what limits an agent's spending and what doesn't.
Create an account and a wallet
Install the CLI and run init:
curl -fsSL https://ln.bot/install.sh | bash
lnbot initlnbot init registers an account, creates its first wallet and saves the account's keys to ~/.config/lnbot/config.json. It prints the wallet's ID (wal_...), its Lightning address (a random one like x4k7m2@ln.bot) and a 12-word recovery passphrase. The passphrase is shown once. Store it somewhere safe: it is how you get back into the account if you lose the keys.
Give every agent its own wallet. lnbot wallet create adds one to the account and prints its ID and address.
The keys init saved are user keys (uk_...), and a user key reaches every wallet on the account. An agent should get a wallet key (wk_...), which reaches one wallet. The CLI doesn't create wallet keys; one API call does, and the key field of its response is the new key, shown once:
curl -X POST https://api.ln.bot/v1/wallets/wal_.../key \
-H "Authorization: Bearer uk_..."The same steps in TypeScript (npm install @lnbot/sdk). Register once, then create a wallet and a key for each agent:
import { LnBot } from "@lnbot/sdk";
// Registering needs no key
const account = await new LnBot().register();
// Store account.primaryKey (uk_...) and account.recoveryPassphrase now
const ln = new LnBot({ apiKey: account.primaryKey });
const wallet = await ln.wallets.create();
console.log(wallet.walletId, wallet.address); // wal_... x4k7m2@ln.bot
// The agent's key: it reaches this wallet only. One per wallet, shown once.
const { key } = await ln.wallet(wallet.walletId).key.create();In Python the calls are LnBot().register(), LnBot(api_key=...).wallets.create() and ln.wallet(wallet_id).key.create().
Connect the agent over MCP
Every wallet has an MCP server at https://api.ln.bot/v1/wallets/{walletId}/mcp (streamable HTTP) with 12 tools: get_wallet, create_invoice, quote_payment, send_payment, list_invoices, list_payments, list_transactions, list_addresses, buy_address, create_l402_challenge, pay_l402_challenge and verify_l402_token. MCP clients and agents can find it through https://ln.bot/.well-known/mcp.json, which describes the endpoint and its tools. MCP can't create wallets or keys, so do the previous step first.
lnbot mcp config --remote prints the config block for the CLI's active wallet. It puts your primary user key in the Authorization header: replace that with the wallet's key before you give the block to the agent's MCP client.
{
"mcpServers": {
"lnbot": {
"headers": {
"Authorization": "Bearer wk_..."
},
"type": "url",
"url": "https://api.ln.bot/v1/wallets/wal_.../mcp"
}
}
}The agent then has the tools alongside its others. Asked to pay r7vq3np@ln.bot ₿500, it can call quote_payment to see the fees, then send_payment with target and amount. Nothing on the server limits what send_payment or pay_l402_challenge spends apart from the wallet's balance; the last section comes back to that. The MCP reference lists each tool's parameters.
Or connect with an SDK
For agents you write yourself (a worker, a framework tool, a script), use an SDK. TypeScript:
import { LnBot } from "@lnbot/sdk";
const ln = new LnBot({ apiKey: process.env.LNBOT_API_KEY }); // the wallet's wk_ key
const w = ln.wallet(process.env.LNBOT_WALLET_ID!); // wal_...
const { available } = await w.get(); // spendable balancePython (pip install lnbot):
import os
from lnbot import LnBot
ln = LnBot(api_key=os.environ["LNBOT_API_KEY"]) # the wallet's wk_ key
w = ln.wallet(os.environ["LNBOT_WALLET_ID"])
available = w.get().available # spendable balanceThe rest of this guide uses w from these snippets. The Go, Rust and C# SDKs have the same operations (SDK reference). None of the SDKs retry a failed request on their own: the error reaches your code, and the idempotency key shown below makes a payment safe to send again.
Get paid
The wallet's Lightning address takes payments from any Lightning wallet, in whatever amount the payer chooses between ₿1 and ₿1,000,000. It is wallet.address when you create the wallet, and w.addresses.list() returns it later. Use it when the payer sets the amount: a top-up, a tip, another agent settling up.
To charge a set amount for a piece of work, create an invoice:
const invoice = await w.invoices.create({
amount: 1000,
memo: "Report #42", // shown in the payer's wallet
reference: "job-42", // yours, for matching the payment to the job
});
console.log(invoice.bolt11); // lnbc10u1p...: send this to the payerinvoice = w.invoices.create(amount=1000, memo="Report #42", reference="job-42")
print(invoice.bolt11)From a shell, lnbot invoice create --amount 1000 --memo "Report #42" prints the invoice and waits until it's paid (--no-wait returns at once).
Know when you've been paid
An agent that runs continuously can hold a live stream open. Streams use Server-Sent Events and work with either key. To wait for one invoice:
for await (const event of w.invoices.watch(invoice.number, 300)) {
console.log(event.event); // "settled" or "expired"
}300 is how long to wait, in seconds, and the most the API allows; the default is 60. If the stream ends without an event, w.invoices.get(invoice.number) returns the invoice with its status: pending, settled or expired. An invoice paid just before it expired can still turn settled after expired, so check it again before writing it off.
Payments to the address have no invoice to watch in advance, so stream the wallet's events instead:
for await (const event of w.events.stream()) {
if (event.event === "invoice.settled") {
console.log("received", event.data.amount);
}
}for event in w.events.stream():
if event.event == "invoice.settled":
print("received", event.data["amount"])A service that would rather be called back than hold a connection open can register a webhook, and ln.bot sends an HTTP POST to it for wallet events such as a settled invoice:
lnbot webhook create --url https://agent.example.com/hooks/lnbotThe command (or w.webhooks.create({ url })) returns a signing secret once. Keep it: it is how you check that a request came from ln.bot. A wallet can have up to 10 webhooks.
Send a payment
// An address needs an amount; an invoice carries its own
let payment = await w.payments.create({
target: "r7vq3np@ln.bot",
amount: 500,
idempotencyKey: "job-42-payout",
});
// The first answer can be "processing": ask again until it settles or fails
while (payment.status === "pending" || payment.status === "processing") {
await new Promise((resolve) => setTimeout(resolve, 500));
payment = await w.payments.get(payment.number);
}
console.log(payment.status, payment.failureReason);import time
payment = w.payments.create(target="r7vq3np@ln.bot", amount=500, idempotency_key="job-42-payout")
while payment.status in ("pending", "processing"):
time.sleep(0.5)
payment = w.payments.get(payment.number)
print(payment.status, payment.failure_reason)To pay an invoice, pass it as the target and leave out amount. w.payments.watch(payment.number) streams the result instead of polling.
Paying another agent is this same call with that agent's address. Between two ln.bot wallets the payment is free and settles in under a second. To a wallet elsewhere on Lightning it costs 0.25% (minimum ₿1) plus the network fee, another 0.25% and routing, usually settles within a few seconds, and can fail. w.payments.resolve({ target, amount }) prices it first: networkFeeEstimate, serviceFee and total. Leave maxFee out; it caps the network fee at 1% of the amount, at least ₿20, and a quote with feeWarning set says when that is too low. The idempotencyKey makes a retry safe: the same key returns the first payment instead of sending a second one.
From a shell, lnbot pay r7vq3np@ln.bot --amount 500 shows the fees, asks you to confirm (skip that with --yes) and then waits for the result.
Pay L402 APIs within a budget
Some APIs charge per request with L402: they answer 402 Payment Required with a Lightning invoice and serve the request once it's paid (how L402 works). In TypeScript, @lnbot/l402 wraps fetch so the agent pays those on its own, inside limits you set. It re-exports LnBot, so it's the only package you need:
npm install @lnbot/l402import { l402, LnBot, L402BudgetExceededError } from "@lnbot/l402";
const ln = new LnBot({ apiKey: process.env.LNBOT_API_KEY }); // the wallet's wk_ key
const api = l402.client(ln, {
walletId: process.env.LNBOT_WALLET_ID!,
maxPrice: 100, // refuse any invoice over ₿100
budgetSats: 50_000, // and any payment past ₿50,000 in a day
budgetPeriod: "day",
});
try {
const report = await api.get("https://api.example.com/v1/report");
console.log(report);
} catch (err) {
if (!(err instanceof L402BudgetExceededError)) throw err;
console.log("not paid:", err.message); // over maxPrice or the day's budget
}When a response is a 402, the client reads the amount from the BOLT11 invoice itself, not from the price the server's response states. It refuses before paying if that amount is over maxPrice (₿1,000 when you don't set it), if it would take the period's spending past budgetSats, or if the invoice has no amount. Otherwise it pays from the wallet, retries the request with the token, and keeps the token for later requests unless the server marks it single-use ("reusable": false). api.fetch(url, init) works like fetch, and get, post, put, patch and delete return the parsed JSON.
The budget is a counter in this process's memory. It starts from zero when the process restarts, it counts invoice amounts but not fees, and it knows nothing about payments made any other way, over MCP or with payments.create().
Python has no L402 client library, so check the price yourself: read the amount from the invoice with payments.resolve(), then pay with l402.pay(). Keep your own running total if the agent needs a budget.
import re
import httpx
MAX_PRICE = 100
url = "https://api.example.com/v1/report"
r = httpx.get(url)
if r.status_code == 402:
challenge = r.headers["www-authenticate"]
bolt11 = re.search(r'invoice="([^"]+)"', challenge).group(1)
price = w.payments.resolve(target=bolt11).amount # from the invoice itself
if price is None or price > MAX_PRICE:
raise RuntimeError(f"refusing to pay {price}")
paid = w.l402.pay(www_authenticate=challenge) # waits for the payment to settle
if paid.authorization is None:
raise RuntimeError(f"payment {paid.status}")
r = httpx.get(url, headers={"Authorization": paid.authorization})Over MCP, the pay_l402_challenge tool takes the WWW-Authenticate value from a 402 and returns the Authorization header to retry with. The agent needs its own way to make the HTTP requests, and the tool pays whatever the invoice asks, up to the balance.
Earn as well as spend
The same wallet can take payments for the agent's own work. Put an L402 paywall in front of an endpoint and each request pays the agent's wallet before your handler runs. Pay-per-API-call with Bitcoin micropayments sets up the paywall, and How to build an AI agent that earns Bitcoin builds a paid summarizer from start to finish.
Limit what the agent can spend
What limits an agent's spending, and where those limits stop:
- One wallet per agent. Each has its own balance, address and key, so a mistake by one agent stays in its wallet.
- Fund the wallet with only what the agent may spend. The balance is the one spending limit ln.bot enforces. Top it up from your main wallet as needed: moving money between ln.bot wallets is free.
- Give the agent its wallet's
wk_key and keepuk_keys to yourself. Nothing is narrower than a wallet key: it reads and spends everything in its wallet, and there are no read-only keys. - Send retries with the same
idempotencyKey, so a payment repeated after a timeout goes out once. - If a key leaks, replace it.
w.key.rotate()issues a new wallet key and the old one stops working at once;w.key.delete()revokes it outright.lnbot key rotate 0andlnbot key rotate 1replace the account's two user keys. - To retire an agent, revoke its key and pay its balance back to your main wallet. There is no call that deletes a wallet.
- Treat the balance as a hot wallet. ln.bot runs the Lightning nodes and the channel liquidity, and balances sit on those nodes. Keep what agents need for near-term spending there, and savings elsewhere.
- The MCP server has no spending cap.
send_paymentandpay_l402_challengepay what the model asks for, up to the balance. maxPriceand the budget inl402.clientapply only to payments that client makes, and the budget resets when the process restarts.
Where to go next
- MCP reference: every tool and its parameters
- SDK reference: TypeScript, Python, Go, Rust and C#
- Pay-per-API-call with Bitcoin micropayments: the server side of L402
FAQ
How do I give my AI agent a Bitcoin wallet?
Run lnbot init, or call register() and then wallets.create() in an SDK. You get an account, a wallet with its own Lightning address and a user key. Create a wallet key for that wallet and give the agent that key, over MCP or in an SDK client.
Can AI agents pay for L402 APIs automatically?
Yes. The TypeScript client l402.client sees the 402, reads the amount from the Lightning invoice, pays it if it is within maxPrice and the budget you set, and retries the request. Over MCP, the pay_l402_challenge tool pays a challenge, with no price limit of its own.
How does an agent connect to ln.bot over MCP?
Every wallet has an MCP server at https://api.ln.bot/v1/wallets/{walletId}/mcp with 12 tools. lnbot mcp config --remote prints the client config, and agents can find the server through https://ln.bot/.well-known/mcp.json. MCP can't create wallets, so create the wallet and its key first.
How do I stop an agent from spending too much?
Give it its own wallet holding only what it may spend, and that wallet's wk_ key. There are no read-only or narrower keys: a wallet key can spend the whole wallet. The MCP tools have no spending cap, and the L402 client's maxPrice and budget live in your process's memory.
Can agents pay other agents directly?
Yes. Each agent's wallet has a Lightning address, and one agent pays another's address with payments.create(). Between two ln.bot wallets the payment is free and settles in under a second. To a wallet elsewhere on Lightning it costs 0.25% plus the network fee, another 0.25% and routing, and usually takes a few seconds.