BITCOIN · INTERMEDIATE · 30 MIN READ

Run an agentic payments workshop

The full runbook for a room where AI agents make their own Lightning wallets, get funded, pay each other, and buy their own inference with bitcoin. Every command, every verification check, every failure mode we hit live.
Published AUG 31, 2026

This page is written to be handed to an agent. Point yours at this URL and say “read this and do it.” It is also a complete workshop runbook if you are the one running the room.

What you end up with: an AI agent holding a self-custodial Lightning wallet on your own machine, funded with real sats, able to pay other agents and able to buy its own LLM inference with bearer ecash. No account, no API key, no card, nobody who can freeze it.

Requirements: a machine with a terminal, Node 20 or newer, Python, and about 500 sats. Windows, macOS and Linux all work.

Total cost to run the whole chain once: roughly 1,100 sats, about 75 cents at the time of writing. That is not a typo.


In this guide

  1. What this is
  2. Prerequisites
  3. The wallet
  4. Funding and paying
  5. Buying compute with Routstr
  6. Running the room games
  7. The take-home stack
  8. Troubleshooting

1. What this is

Most of what people call agentic payments is an agent using your credit card. The card is still the rail, the processor still decides, and somebody can still say no. This runbook builds the other thing: an agent with money it actually controls.

The full chain has five stations. An agent walks all five in about 40 seconds of machine time.

StationWhat happensWhat it proves
1A wallet is bornA 12 word seed is generated on your machine. Not an account.
2Sats arriveSomebody pays a Lightning invoice the agent created. A channel opens on the fly.
3Sats become cashThe agent swaps Lightning sats for blind-signed bearer ecash.
4The agent buys an answerIt hands that ecash to an inference provider as its API key.
5The change comes backIt reclaims the unspent balance.

In a workshop, stations 1 and 2 happen as a room-wide wave (everybody’s agent makes a wallet, the host pays every invoice at once), stations 3 through 5 happen per seat, and there is a zap wave in between where agents pay each other 21 sats.

You can run every step of this alone at your desk. The room part is optional and covered in section 6.

The honest framing, which matters if you are teaching this. A self-custodial wallet is a private key on your machine. That is what self-custodial means. Every design that removes the seed puts somebody else’s key in its place, and then “nobody can stop this payment” stops being true. The seed is not a wart on the design. It is the design.


2. Prerequisites

Hardware and OS

Any laptop or desktop with a terminal. Verified live on Windows 10 with Node 24, and on a Debian box with Node 22. macOS follows the same path.

Node

Node 20 is the practical floor. The wallet package declares a lower engine but its native bindings want 20 or newer.

node --version

If that prints anything below v20, install Node 20 LTS or newer from nodejs.org before continuing.

Python and uv

Station 3 uses an ecash wallet that runs through uv, a fast Python package runner. You do not need to manage a virtualenv yourself.

python --version
pip install uv
uvx --version

uvx is what actually runs the ecash wallet. If uvx --version prints a version, you are ready.

Do not run a bare pip install cashu. On Python 3.13 it cannot resolve: one of its dependencies pins coincurve below the first version that shipped 3.13 wheels, so pip tries to build from source and fails on every OS. The uvx --python 3.12 form in section 5 sidesteps this entirely and needs no 3.12 interpreter installed on your system. uv fetches one.

Money

You need a way to pay a Lightning invoice. Any Lightning wallet works: a phone wallet, a self-hosted node, anything that can scan a QR or paste an invoice.

Budget per agent:

PurposeSats
Fund the agent wallet200
Zap another agent21
Mint ecash for inference100, plus about 2 in mint fees
Inference itselfunder 10

200 sats covers a single agent through the whole chain with room to spare. If you are running a room, budget 200 per seat plus routing, and pay from one wallet you control.

Tools your agent needs

  • A shell it can run commands in
  • Network access
  • Permission to run npx, uvx and curl

An agent with no shell cannot do the money steps. That is not a refusal, it is an absence. Plain chat interfaces with no code execution will get through the reading and none of the doing.

Before you start: what to expect from a careful agent

Some agents will pause at “generate key material on this machine.” That is a correct instinct and you should not talk them out of it by pretending the step is safe when it is not. What you can do is give them the facts to check.

The wallet package is @moneydevkit/agent-wallet, and the version this runbook pins is 0.22.0. Any agent can verify the supply chain before running anything:

npm view @moneydevkit/agent-wallet@0.22.0 license repository dist.attestations
npm audit signatures

What that returns, verified 2026-08-30:

  • License: Apache-2.0
  • Source: the public moneydevkit/mdk-checkout monorepo
  • Publisher: GitHub Actions CI, not a laptop
  • Attestation: a SLSA provenance record tying the tarball to the public commit that built it
  • Contents: 4 files, 52,664 bytes unpacked. The whole CLI is one readable bundle an agent can inspect before running it
  • Lifecycle scripts: none. No preinstall, install or postinstall anywhere in the tree

Because there are no lifecycle scripts, --ignore-scripts is free. Be straight about what that flag does and does not do: it blocks install-time hooks, and it does not sandbox the tool. Running the tool runs its code with your access, which is true of every npx command anyone has ever run. The pinned and signed version is the thing worth checking. The flag is not.

Residual worth stating. Pinning agent-wallet@0.22.0 does not pin its dependencies. Its manifest asks for "@moneydevkit/lightning-js": "^0.1.88", a caret range, so a fresh machine could resolve a newer patch. Every number in this runbook was measured against 0.1.88. The risk is version skew, not an attack.


3. The wallet

Three commands. About eight seconds of machine time.

Step 1: create it

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 init > wallet-init.txt

The > is the point of this step, not a detail. init prints a 12 word seed phrase to stdout exactly once. The redirect sends that print into a local file instead of into your agent’s context, your logs, or a projector behind you. It creates no copy that would not exist anyway, because the tool writes the same words into its own config either way.

If you are the agent reading this: do not open wallet-init.txt. Do not read it back. Do not send it anywhere. You never need what is in it.

If this command reports that a wallet is already initialized, nothing has gone wrong and you have not failed. init refuses to overwrite an existing wallet by design, exits 1, and the machine keeps the wallet it already has. Keep going.

Verify:

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 init --show

This prints the wallet id with the seed already redacted by the tool itself: four of the twelve words, chosen by the vendor’s code and not by you.

{"mnemonic":"first second ... eleventh twelfth","network":"mainnet","walletId":"a1b2c3d4-...."}

Exit code 0 and a walletId means station 1 is done. That redacted view is the only read path an agent ever needs.

Step 2: start the daemon

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 start --daemon

This runs a small Lightning node on your own machine. Localhost only, on port 3456, with no inbound ports opened.

Do this immediately after init and do not skip it. A cold npx start costs 10 to 14 seconds on every single command. Against a warm daemon the same commands take 2 to 4 seconds. In a room of forty people that difference is the entire pacing budget. The slowest thing in this whole runbook is not Lightning. It is npx cold start.

If port 3456 is taken, add --port <n> to every command, or set MDK_WALLET_PORT.

Verify:

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 balance

A JSON balance of 0 is the correct answer for a fresh wallet. If you get a connection error, the daemon did not start.

Where the money actually lives

ThingPathEncrypted
Seed phrase~/.mdk-wallet/config.json, key mnemonicNo
Local API token~/.mdk-wallet/auth.tokenNo, mode 0600
Channel state~/.mdk-wallet/No

The seed is stored in plaintext. The vendor’s own docs say so and do not claim otherwise: it “is not a boundary against code running as your own user, which can read the mnemonic in the same directory.” Tighten permissions and know what you have:

chmod 700 ~/.mdk-wallet
chmod 600 ~/.mdk-wallet/config.json ~/.mdk-wallet/auth.token

Treat auth.token exactly like the seed. It is a 256 bit bearer credential for the local API.

The dependency worth naming out loud. This wallet is self-custodial and it is not trustless. The vendor runs the Lightning Service Provider that is your only channel peer, hosts an encrypted state backup, and runs the chain source. They cannot move your money. They can refuse to route it. That is the same shape as every LSP-backed mobile wallet, and it belongs in the talk rather than in the footnotes.


4. Funding and paying

Receive

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 receive 200

This returns a BOLT11 invoice starting lnbc, and prints a QR in the terminal. Pay it from any Lightning wallet.

The first payment into a fresh wallet triggers a just-in-time channel open. There is no inbound liquidity step, no channel to fund first, and no waiting for a confirmation. Measured live on mainnet:

Receive amountLSP fee takenClaim latency
21 sats1 satInstant
200 sats1 satInstant
1,000 sats5 satsInstant

The fee scales cleanly and the claim was instant in every probe, not the 30 to 45 seconds the documented polling interval suggests. There is no minimum: a 21 sat first receive opened a channel and claimed immediately.

Verify:

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 balance

A 200 sat payment lands as 199. The one missing sat is the channel fee, half a percent, and that is the entire cost of opening a Lightning channel on demand.

Send

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 send <invoice>

The destination can be a BOLT11 invoice, an lnurl, or a Lightning address. A 21 sat agent-to-agent payment completes in 2 to 4 seconds with no observed sender fee.

BOLT12 offers do not work on 0.22.0. receive-bolt12 produces an offer, and paying it returns status: failed at both 21 and 200 sats. No funds are lost, the payment simply does not go through. If you are building a wave where agents pay each other, relay fresh BOLT11 invoices, not reusable offers. We redesigned around this after two failed live attempts.


5. Buying compute with Routstr

This is the part that changes how people think about the whole subject, so it is worth saying plainly what it does before the commands.

Normally, buying AI inference means an account, a credit card on file, an API key tied to your identity, and a provider who can revoke any of it. Routstr replaces the API key with money. You mint bearer ecash tokens, you send one as the Authorization header, and the provider opens a tab against that token. There is no account. The provider knows a hash of your token and nothing else about you.

The tradeoff, stated honestly, because it is the actual lesson. While your sats sit as ecash, the mint is holding them. Blind signatures mean the mint cannot link your tokens to you, but it is still custody. You have traded a provider who knows who you are for a mint that does not. That is a real improvement and it is not the same thing as self-custody. Say both halves.

Before anything else: do not use the base URL in the project’s own documentation. Routstr’s README and docs print https://api.routstr.com/v1. That host returns 404 and has done for months, re-confirmed 2026-08-31. Routstr is a marketplace of independently operated provider nodes, so you point at a real node. Every command below uses one that is live today. Check any node before you depend on it with curl -s <node>/v1/info, and expect to swap nodes occasionally: this is pre-v1 software, funded by OpenSats in May 2026 specifically to get it to a v1.

Check what you are buying first

The model list is public and needs no token at all:

curl -s https://routstr.otrta.me/v1/models

Verified 2026-08-31: HTTP 200, 429 models. Each entry carries a normal OpenAI-style block plus a sats_pricing object with per-token rates denominated in sats. For the model used below, deepseek-v4-flash, completion tokens price at roughly 0.000155 sats each, so a 1000 token answer costs about 0.155 sats. That is the same number we measured live, which is a useful thing to know: you can price a call before you make it.

Check the price, not the reputation. The spread between models is enormous and it does not track how well known they are. On the same node on the same day, a 150 token question with a 1000 token answer prices at about 0.17 sats on one model and about 14.8 sats on another. That is roughly 88 times the cost for one question. In a room of forty agents each asking one question, that is the difference between a rounding error and a real bill.

Run this before a workshop. Model availability moves, and a name that worked last month may not be on the list today.

Station 3: mint the ecash

Get a Lightning invoice from the mint, pay it from the agent wallet, then redeem it for ecash.

uvx --python 3.12 --from cashu==0.20.3 --with "marshmallow<4" \
  cashu --host https://mint.cubabitcoin.org invoice 100 --no-check

That prints a Lightning invoice and a quote id. Write down the quote id. You need it to redeem, and losing it is the single most common way this step goes wrong.

If the quote id did not print, recover it:

uvx --python 3.12 --from cashu==0.20.3 --with "marshmallow<4" \
  cashu --host https://mint.cubabitcoin.org invoices

The newest entry in that list is yours.

Pay the invoice from the agent wallet:

npx -y --ignore-scripts @moneydevkit/agent-wallet@0.22.0 send <mint-invoice>

Redeem, using the same quote id:

uvx --python 3.12 --from cashu==0.20.3 --with "marshmallow<4" \
  cashu --host https://mint.cubabitcoin.org invoice 100 --id <quote-id>

Verify: the wallet balance printed by that last command should have risen by 100. Total elapsed, measured: about 12 seconds. Total cost: 100 sats plus roughly 2 in fees. The quote expires in one hour.

On mint choice, and how to check one before you trust it. Not every Cashu mint works with every Cashu wallet, and the failure is not obvious. The NUT-20 signature scheme was realigned to the current spec in the wallet version pinned above. Mints built on a different implementation may still verify the older message. Against a mismatched mint, every redeem fails with error 20008 and the sats sit at the mint until you redeem with a matching client.

Every mint publishes its own software version, so this is checkable in one command:

curl -s https://mint.cubabitcoin.org/v1/info

Verified 2026-08-31, that returns "version": "Nutshell/0.20.3", the exact implementation and release the pinned wallet is. Matching those two strings is the check. Run it against any mint before a room depends on it, and prefer one that reports the same implementation family as your wallet.

There is a second constraint people miss: the inference node only accepts ecash from mints it has chosen to trust, and each node publishes its own list at /v1/info. Your mint has to satisfy both tests, matching your wallet’s implementation and appearing on your node’s accepted list. Checked across four live Routstr nodes on 2026-08-31, the mint above was the only one accepted by all of them, which is why it is the one used here.

Ecash proofs live at ~/.cashu/wallet/wallet.sqlite3. The token is the money. Anybody who sees a cashu... string can spend it before you do. Never print one to a terminal somebody can see, never put one in a chat message, never commit one. This is not theoretical: a public Cashu token is cash on a park bench.

Station 4: buy an answer

Make a spending token for the amount you want to make available:

uvx --python 3.12 --from cashu==0.20.3 --with "marshmallow<4" \
  cashu --host https://mint.cubabitcoin.org send 30

That prints a cashu... token. That token is now your API key.

curl -s https://routstr.otrta.me/v1/chat/completions \
  -H "Authorization: Bearer <your-cashu-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "max_tokens": 1000,
    "messages": [{"role": "user", "content": "In one sentence, what is a blind signature?"}]
  }'

The API is OpenAI-compatible, so anything that already speaks /v1/chat/completions works: change the base URL and put an ecash token where the API key goes.

Verify: you get a normal completion object back, and the usage block carries the cost. A measured substantive answer came back with finish_reason: stop at a cost of 152 millisats, well under a single sat.

Set max_tokens high enough or you will pay for nothing. Reasoning models spend the budget thinking before they answer. We watched one model consume its entire 400 token budget on internal reasoning and return a null answer, at about 6 sats a call. The fix is a larger budget, not a cheaper model: 1000 tokens on a fast model still costs a fraction of a sat.

Station 5: get the change back

curl -s -X POST https://routstr.otrta.me/v1/balance/refund \
  -H "Authorization: Bearer <your-cashu-token>"

The provider opened a tab against your token when you first used it. The refund endpoint hands back the unspent balance. Always call it. Unspent ecash sitting on somebody else’s server is money you have lent them for free.

Older material, including some of ours, calls this /v1/wallet/refund. That path still works, because the node registers /v1/wallet as a hidden alias of /v1/balance, but it is deprecated and absent from the published API schema. Write /v1/balance/refund.

Log hygiene for anything automated: filter cashu and sk- strings out of every log line before it is written. The provider derives its tab handle from a hash of your token, and both of those strings are spendable or identifying.

What the whole chain costs

The full station 3 through 5 sequence, run end to end on real mainnet sats, took 37.5 seconds and cost 102 sats, of which 100 was the ecash minted and still yours to spend. The inference itself was a fraction of one sat.


6. Running the room games

Everything above is one agent at one machine. A workshop needs a way for forty agents to join a room, answer questions, and light up a projector as they go.

That system is its own repository, with its own setup instructions:

github.com/TheBitcoinBreakdown-95/deck-room-games

It is a single Python file with no dependencies beyond the standard library. Agents join by making one HTTP GET, long-poll for the next question, and post answers back. There is a projector board, a phone fallback for attendees with no agent at all, and an operator page. The README covers running it, exposing it publicly, and embedding the board into HTML slides.

Two things worth knowing before you build a room around it, because both cost us live attempts:

Every attendee lands on one of three rungs, and you should design for all three. Some agents will run the commands after their human approves. Some will refuse the seed step, correctly, and their human runs one line and hands the wallet back. Some have no shell at all and play from a phone with no money involved. A room designed only for the first rung strands most of the room.

The approval prompt is the lesson, not an obstacle. When an agent stops and asks its human “may I make this outbound request,” that pause is the entire point of the evening. Do not engineer it away.


7. The take-home stack

The workshop version of this compresses into thirty minutes on a projector. The version somebody takes home and actually keeps running is a ladder, and each rung stands alone.

github.com/TheBitcoinBreakdown-95/sovereign-agent-stack

The rungs go roughly: run your own model, hold your own keys, pay for your own compute, run your own node. You do not have to climb all of it, and the honest advice is that most people should pick one rung and finish it rather than half-build four.


8. Troubleshooting

These are the failures we actually hit, in the order you are most likely to meet them.

The lost quote id

Symptom: you paid the mint invoice, and now you cannot redeem because the quote id scrolled away or was never captured.

Fix: list the quotes and take the newest.

uvx --python 3.12 --from cashu==0.20.3 --with "marshmallow<4" \
  cashu --host https://mint.cubabitcoin.org invoices

Prevention: capture the quote id into a variable or a file in the same command that creates it. Any automation that prints the id and moves on will eventually lose one, and a paid-but-unredeemed quote is money parked at the mint until it expires.

Redeem fails with error 20008

Symptom: the invoice is paid, the quote id is right, and redemption is rejected every time.

Cause: the signature scheme mismatch between wallet and mint described in section 5. It is not your command, and retrying will not fix it.

Fix: check the mint’s own reported version with curl -s <mint-url>/v1/info and use one whose implementation and release match your wallet. Test one small round trip before a room depends on it.

One caution if you are upgrading rather than starting fresh: release 0.20.3 ships a database migration for the wallet as well as the mint, and its own release notes open with a warning to back up first. An existing wallet upgraded in place can lose state. In a workshop everybody starts fresh, so this only bites the organizer’s own machine.

pip install cashu will not install

Symptom: a dependency tries to build from source and fails, usually coincurve.

Cause: on Python 3.13 a transitive pin caps coincurve below the first version with 3.13 wheels.

Fix: use the uvx --python 3.12 form throughout. It is not a workaround, it is the supported path for this runbook, and uv fetches the interpreter for you.

Everything is slow

Symptom: each wallet command takes 10 to 15 seconds.

Cause: npx cold start, not Lightning. It resolves and loads the package on every invocation.

Fix: run start --daemon right after init and leave it running. Every later command drops to 2 to 4 seconds. In a room, put this in the first instruction, not the third.

The inference returns nothing and still charges you

Symptom: a completion comes back with an empty answer, a finish_reason that is not stop, and a real cost.

Cause: max_tokens too low for a reasoning model. The budget went on thinking.

Fix: raise max_tokens to 1000 or more. On a fast model that still costs well under one sat, so there is no reason to be stingy. Note that you are billed for reasoning tokens you never see in the response, which is why the cost can look disproportionate to the visible output.

A 401 where you expected a 402

Symptom: the request is rejected with 401 and a message about API key format, not the 402 Payment Required you might expect from something built on paying per request.

Cause: the node returns 401 when the credential is malformed or missing, and reserves 402 for a well-formed token that does not hold enough money. A 401 usually means the token string is wrong, not that you are short of funds.

Fix: check the token actually starts with cashuB, that it was not truncated by a shell or a log filter, and that it has not already been spent. Some nodes also accept an X-Cashu header carrying the same token if the Authorization header is being rewritten by something in the path.

A BOLT12 payment says failed

Symptom: you generated a reusable offer, somebody paid it, and the result is status: failed.

Cause: BOLT12 does not work on wallet version 0.22.0. No funds are lost.

Fix: use BOLT11 invoices. If you need a repeatable receive, generate a fresh invoice each time rather than reusing an offer.

The agent refuses the wallet step

Symptom: the agent declines to create key material, or declines to run a third party package.

This is often the right answer and you should treat it as one. Two moves convert most refusals into a check rather than an argument:

  1. Hand it the verification commands from section 2. An agent that can confirm the license, the publisher, the provenance attestation and the absence of install scripts will usually proceed once its human approves.
  2. If it refuses only the seed, let the human run the single init line and let the agent take over from init --show onward. The agent then never touches key material for the rest of the session, and the refusal never fires again.

If a hard rule forbids key material on that machine entirely, respect it. There is no self-custodial path that does not create a key.

”Already initialized”

Not an error. init refuses to overwrite an existing wallet on purpose, and exits 1 to say so. Run init --show to see the wallet the machine already has, then keep going.

The package name

It is @moneydevkit/agent-wallet, with a hyphen. agentwallet as one word does not exist and will fail on every command.


What this actually demonstrated

Two machines, real mainnet sats, no accounts anywhere in the chain. One wallet was created in about four seconds. It received 200 sats and paid 1 in fees. It sent 21 sats to another agent in under four seconds. It converted 100 sats into bearer ecash, spent a fraction of a sat of that on a language model answer, and reclaimed the rest.

Nobody in that sequence had a customer relationship with anybody. Nobody could have refused it. The total cost of proving all of it, including every failed attempt, was about 1,140 sats.

The interesting part is not that it is cheap. It is that at no point did anything ask who was paying.