Lightning Wallet as a Service: One Wallet per User or Agent
How a platform gives every user, seller or agent its own Lightning wallet on ln.bot: keys, funding, spending limits and events, compared with embedded wallets.
Wallet as a service (WaaS) means an API creates wallets for your application and your code moves money between them. The category is crowded. Fireblocks, Circle, Coinbase, Magic and Privy all sell it; Stripe agreed to buy Privy in June 2025, and Fireblocks bought Dynamic that October. Most of it is built for stablecoins and tokens on EVM chains and Solana, around key management and login flows for people.
This page is about a platform whose users, sellers or agents each need a balance that code controls and that pays over Lightning. It covers ln.bot's model, the patterns that work with it, what ln.bot is and isn't, and how it compares with the embedded-wallet providers as of October 2026.
The wallet is the primitive
A payment processor such as Stripe or OpenNode gives you one merchant balance and lets you create charges against it. A wallet service turns that around: each participant in your system gets its own balance, address and history. Users, sellers, agents, escrow accounts and the platform's own treasury are all wallets, and the platform moves money between them.
That changes the code. A marketplace stops recording that it owes a seller ₿4,200 and settling at the end of the month: the seller's wallet holds ₿4,200, and the seller can spend it. An agent stops asking a person for a card: it has a balance to pay from and an address other agents can pay.
The model: an account, a wallet per user, a key per agent
Three calls set it up on ln.bot.
Register once. register() needs no authentication and returns the account's two user keys (uk_...) and a recovery passphrase. A user key reaches every wallet on the account, so it belongs on your backend and nowhere else.
import { LnBot } from "@lnbot/sdk";
const account = await new LnBot().register(); // no auth, once per platform
// account.primaryKey, account.secondaryKey: "uk_..." user keys,
// each reaching every wallet
// account.recoveryPassphrase: 12 words that restore access if both keys are lostThen, for each user, seller or agent, create a wallet with the user key. wallets.create() takes no arguments and returns the wallet's ID and a random Lightning address; it doesn't return keys. If an agent will use the wallet, give it a wallet key: key.create() makes a wk_... key, returned once, that reaches that wallet and no other. A wallet has at most one. Either key can rotate or revoke it, so an agent holding the wallet key could replace it, and the user key can always rotate it back.
import { LnBot } from "@lnbot/sdk";
// The account's user key (uk_...), on your backend only
const platform = new LnBot({ apiKey: process.env.LNBOT_USER_KEY });
async function onboard(userId: string) {
// No arguments. The address is a random hashid, like "k3p9tq@ln.bot"
const { walletId, address } = await platform.wallets.create();
// "wk_...": returned once, reaches this wallet only
const { key } = await platform.wallet(walletId).key.create();
await saveWallet({ userId, walletId, address, walletKey: key }); // your database
return { walletId, address };
}The same in Python, where every argument is keyword-only:
from lnbot import LnBot
platform = LnBot(api_key="uk_...") # the account's user key, backend only
def onboard(user_id: str) -> str:
wallet = platform.wallets.create() # wallet_id, name, address
key = platform.wallet(wallet.wallet_id).key.create() # key.key: "wk_..."
save_wallet(user_id, wallet.wallet_id, wallet.address, key.key) # your db
return wallet.addressThere is no limit on the number of wallets, and no call that deletes one. There are no read-only keys or scopes either: a wallet key can do anything with its own wallet, including spend all of it. Every wallet call accepts either kind of key, live event streams (events.stream(), invoices.watch(), payments.watch()) included; creating wallets takes the user key. The API reference lists every call, and the SDKs cover Go, Rust and C# as well.
A wallet per agent
An agent needs two values: its wallet ID and its wallet key.
import { LnBot } from "@lnbot/sdk";
// Inside the agent: its wallet key and wallet ID, nothing else
const ln = new LnBot({ apiKey: process.env.AGENT_WALLET_KEY }); // wk_...
const wallet = ln.wallet(process.env.AGENT_WALLET_ID!);
const { available } = await wallet.get();
if (available >= 200) {
// Another agent's address
await wallet.payments.create({ target: "r7vq3np@ln.bot", amount: 200 });
}An agent on MCP needs no code. Every wallet has an MCP server at https://api.ln.bot/v1/wallets/{walletId}/mcp (streamable HTTP), and agents and clients can find it through https://ln.bot/.well-known/mcp.json. lnbot mcp config --remote prints the client block with the account's user key in it; for an agent, put the wallet key in its place:
{
"mcpServers": {
"lnbot": {
"type": "url",
"url": "https://api.ln.bot/v1/wallets/wal_.../mcp",
"headers": { "Authorization": "Bearer wk_..." }
}
}
}The agent then has 12 tools, from get_wallet and create_invoice to quote_payment, send_payment and pay_l402_challenge. The agent payments guide goes through both setups, and L402 covers how an agent pays per request for an API.
Funding from a treasury
Transfers between ln.bot wallets are free and settle in under a second, so funding an agent is a payment from the platform's own wallet to the agent's address. Here a scheduled job brings each agent back up to a daily allowance:
import { LnBot } from "@lnbot/sdk";
const platform = new LnBot({ apiKey: process.env.LNBOT_USER_KEY });
const treasury = platform.wallet("wal_treasury...");
type Agent = { walletId: string; address: string };
// Bring an agent's wallet back up to its daily allowance
async function topUp(agent: Agent, allowance: number, day: string) {
const { available } = await platform.wallet(agent.walletId).get();
if (available >= allowance) return;
await treasury.payments.create({
target: agent.address,
amount: allowance - available,
idempotencyKey: `topup-${agent.walletId}-${day}`, // once per agent per day
});
}
await topUp({ walletId: "wal_...", address: "k3p9tq@ln.bot" }, 5_000, "2026-10-23");Idempotency keys are scoped to the sending wallet, so however many times the job runs on a given day, each agent gets one top-up from the treasury.
Spending discipline
The control that holds is the balance. A wallet key can spend everything in its wallet. The MCP tools have no spending cap of their own. The TypeScript L402 client's maxPrice and budgetSats stop that client from paying more than a set price per request or more than a budget per period, but the budget lives in the process's memory, so a restart resets it, and it doesn't see payments the agent makes any other way. In practice:
- Fund each agent's wallet with what it may spend in a period, and top it up on a schedule, as above.
- Keep the platform's float in a treasury wallet, keep the user key that reaches it away from every agent, and keep larger reserves off ln.bot.
- Price payments that leave ln.bot with
payments.resolve()before sending, leavemaxFeeat its default unless the quote warns the fee is unusually high, and set an idempotency key on anything a job might retry. - When a user leaves or an agent is retired, sweep the balance back and revoke the key.
import { LnBot } from "@lnbot/sdk";
const platform = new LnBot({ apiKey: process.env.LNBOT_USER_KEY });
// When a user leaves or an agent is retired
async function retire(walletId: string, treasuryAddress: string) {
const w = platform.wallet(walletId);
const { available } = await w.get();
if (available > 0) {
// Free between ln.bot wallets, so the whole balance can move
await w.payments.create({ target: treasuryAddress, amount: available });
}
await w.key.delete(); // the agent's wk_ key stops working at once
}
await retire("wal_...", "x4k7m2@ln.bot");The emptied wallet stays on the account, and its address can still receive, but the agent can no longer use it.
Webhooks or an event stream
A platform needs to know when money arrives or a payment fails. ln.bot offers two ways.
The event stream, events.stream(), is Server-Sent Events: one long-lived connection per wallet, opened with either key, carrying invoice.created, invoice.settled, payment.created, payment.settled and payment.failed, with a keepalive every 30 seconds. It suits an agent that watches its own wallet.
import { LnBot } from "@lnbot/sdk";
// SSE: one connection per wallet; its wallet key is enough
const w = new LnBot({ apiKey: "wk_..." }).wallet("wal_...");
for await (const event of w.events.stream()) {
if (event.event === "invoice.settled") console.log("received", event.data.amount);
if (event.event === "payment.failed") console.log("failed", event.data.reference);
}Webhooks turn it around: ln.bot sends an HTTP POST to a URL you register on a wallet (up to 10 per wallet), and each registration gets a signing secret, returned once. A backend serving thousands of wallets receives requests instead of holding thousands of connections, so register the platform's endpoint on each wallet when you create it. Put the wallet's ID in the URL: a delivery doesn't say which wallet it is for, and each wallet's webhook has its own secret.
import { LnBot } from "@lnbot/sdk";
const platform = new LnBot({ apiKey: process.env.LNBOT_USER_KEY });
// Webhooks: ln.bot calls your backend, whatever the number of wallets
const walletId = "wal_...";
const hook = await platform.wallet(walletId).webhooks.create({
url: `https://example.com/hooks/lnbot/${walletId}`,
});
console.log(hook.id, hook.secret); // the secret comes back once: store it with the wallet IDThe API reference shows how to check a delivery's signature.
What ln.bot is, and what it isn't
ln.bot is hosted and custodial. It runs the Lightning nodes and their channel liquidity, every wallet's balance sits on those nodes, and your keys are how you use and move it. Treat a wallet as an operational balance, a hot wallet for near-term spending, and keep reserves on infrastructure you control.
It moves bitcoin over Lightning: no stablecoins and no other tokens. There are no per-key spending policies and no read-only keys. Registering takes no signup form and no identity check.
The pricing has no subscription. Transfers between ln.bot wallets are free, receiving from any Lightning wallet is free, and paying out to Lightning costs 0.25%, with a minimum of ₿1, plus the network fee: another 0.25% and the routing fee. A chosen address name is a one-time ₿100 for five characters or more, ₿1,000 for four or ₿10,000 for three; every wallet's random address is free.
How it compares with embedded-wallet providers
The embedded-wallet providers solve a different problem: wallets on public chains for people, and increasingly for agents, with keys split by MPC or kept in secure hardware. Their products and prices, from their own pages as of October 2026:
- Magic: embedded wallets behind email, SMS and social login, plus server wallets, on 30+ networks including Ethereum, Solana and Bitcoin. Free for 1,000 monthly active wallets and 50,000 signatures, then $0.045 a wallet; $99 a month covers 2,500.
- Privy: user, server and agent wallets with a policy engine. It sends transactions on EVM chains, Solana, Tron and Tempo, signs for Bitcoin and other chains, and creates and pays Lightning invoices through Spark. Free to 499 monthly active users, $299 a month to 2,499, $499 to 9,999, and usage pricing above 10,000.
- Coinbase Developer Platform: non-custodial server and embedded wallets on EVM chains and Solana, and the Agentic Wallet CLI and MCP server for agents. $0.005 per write operation, with the first 5,000 a month free.
- Fireblocks: wallets, custody and treasury tools for institutions on many chains including Bitcoin, now with Dynamic's embedded wallets. Essentials costs $999 a month for up to six months and includes 5,000 embedded wallets; custom plans start at $36,000 a year.
- Circle: developer-controlled and user-controlled wallets on EVM chains, Solana, Aptos, NEAR and others. The first 1,000 monthly active wallets are free, then $0.038 to $0.050 a wallet, falling with volume.
- ln.bot: hosted, custodial Lightning wallets with an API, SDKs, a CLI and an MCP server per wallet, for bitcoin only. No monthly fee; 0.25% (minimum ₿1) plus a network fee of 0.25% and routing on payments out, and free between ln.bot wallets and to receive.
Three differences matter more than the prices.
The first is custody. Magic and Coinbase describe their wallets as non-custodial. Circle's developer-controlled wallets are custodied by the business that runs them, and its user-controlled ones by the users. ln.bot holds the balances itself.
The second is controls. Privy's policy engine can cap transfers per transaction or over a time window and allowlist recipients, and Coinbase meters policy evaluation as a wallet operation, so both enforce limits on the provider's side. ln.bot has no such policies: the limit is what you put in the wallet.
The third is what an agent pays with. Agent wallets aren't unique to ln.bot: Privy has them, and Coinbase has AgentKit and the Agentic Wallet, whose agents hold stablecoins and pay for services over x402. An ln.bot agent pays in bitcoin over Lightning: it gets an address other wallets can pay, transfers to other ln.bot wallets that are free and settle in under a second, and L402 libraries for paying and charging per request. x402 vs L402 compares the two payment protocols.
If your users hold dollars or tokens, need to sign with their own keys, or need spending policies enforced by the provider, one of the five fits. If you need many balances that code controls, paying and receiving bitcoin over Lightning, and the custodial tradeoff above is acceptable, ln.bot does that. A platform can also run both: stablecoin wallets for its users, and an ln.bot wallet for each agent that pays per request.
FAQ
What is wallet as a service?
An API that creates and runs wallets for your application, so each of your users, sellers or agents gets its own balance and address without you building key management or settlement. Most providers sign transactions on EVM chains and Solana; ln.bot runs hosted wallets that pay and receive over Bitcoin's Lightning Network.
How is a wallet API different from a payment API?
A payment API takes charges into one merchant balance. A wallet API gives each participant its own balance, address and history, and your code moves money between them. The wallet is the primitive: platforms use one when every user, agent or service needs money of its own.
How do I create a wallet for each user on ln.bot?
Register once, with no authentication, to get the account's user keys. Then call wallets.create() with a user key for each user: it takes no arguments and returns a wallet ID and a Lightning address. An agent gets that wallet's own key (wk_), which reaches no other wallet.
How much does ln.bot cost?
There is no monthly fee. Transfers between ln.bot wallets and receiving from any Lightning wallet are free; paying out to Lightning costs 0.25%, minimum ₿1, plus the network fee, another 0.25% and routing. A chosen address name is a one-time ₿100, ₿1,000 or ₿10,000, depending on its length.
Does ln.bot hold my funds?
Yes. ln.bot is custodial: balances sit on the Lightning nodes ln.bot runs, and ln.bot operates their channel liquidity. Each wallet is its own balance, reached with its API keys. Treat wallets as operational balances for near-term spending and keep reserves on infrastructure you control.
Can I cap what an agent spends?
Fund its wallet with only what it may spend. A wallet key reaches one wallet but can spend all of it, the MCP tools have no cap of their own, and the L402 client's budget lives in the process's memory. The balance is the limit that holds.