<!-- WoA door public toolkit mirror, no secrets -->
# Wallet of Agents: door toolkit (public)

**Public base:** https://walletofagents.dev
**Discover:** https://walletofagents.dev/woa/discover
**llms.txt:** https://walletofagents.dev/woa/llms.txt
**Spend cap:** You choose your spend limit, up to 21,000 sats, and we enforce it. Set it at claim (`cap_sats`) or any time with `POST /woa/cap`.
**Rip-cord:** Set a return Lightning address, and one call sends your whole balance (less routing fees) to that address only, then freezes the wallet. See `/woa/llms.txt`.
**OpenAPI:** https://walletofagents.dev/woa/openapi.json

## Agent redeem checklist (required)

1. Optional: `GET /woa/redeem?token=...` -> 200 `status: "ready"` (info only, no secrets, does not burn).
2. **Claim:** `POST /woa/redeem` with `{"token":"...","cap_sats":N}` once (cap_sats optional, 1 to 21000) -> save `nwc_url`, `recovery_secret`, and `lud16` privately.
3. **Verify door closed:** GET the link again -> must be **410** `already_used` or **404**. If it still says ready, STOP and tell the operator (token not burned). Restore does not reopen it.
4. Write NWC and recovery secret to files with mode 600. Never paste into chat, Nostr, or Colony.
5. Use via `woa mcp` / HTTP with quote-before-pay, freeze, and your spend cap. Routing fees count against the cap.
6. Change your cap: `POST /woa/cap` with `{"recovery_secret":"woa_rec_...","cap_sats":N}`.
7. Lost NWC only: `POST /woa/restore` with `{"recovery_secret":"woa_rec_..."}`. Save the new `nwc_url` and the new `recovery_secret`.
8. Optional rip-cord: add `"return_address":"name@example.com"` at claim or `POST /woa/return-address` later (cooldown applies). In trouble: `POST /woa/ripcord` with `{"recovery_secret":"woa_rec_...","confirm":true}`.
9. Optional spend log: `POST /woa/pay` with `{"recovery_secret":"woa_rec_...","invoice":"lnbc...","memo":"...","run_id":"..."}`, read back with `POST /woa/ledger`. Refusals carry `reason` and `max_spendable_now_sats`; `POST /woa/cap` shows `low_balance`, `last_topup_at`, `last_topup_sats`.
10. Optional Lightning address: `POST /woa/lnaddress` with `{"recovery_secret":"woa_rec_...","handle":"my-agent"}` gives `my-agent@walletofagents.dev`. Payments to it land in your wallet.

---

# Wallet of Agents: agent toolkit

**Audience:** AI agents that run unattended, and the people wiring them up.
**Status:** early preview. Install the MCP server locally or use the redeem door. Custodial, small hard caps.

Machine index: `/woa/llms.txt` · Discover JSON: `/woa/discover` · Agent card: `/.well-known/agent-card.json`

---

## 1. What Wallet of Agents is

Wallet of Agents gives an AI agent its own small Lightning wallet: one redeem, its own Lightning address, a hard spend cap, custody stated up front.

| Layer | Role |
|-------|------|
| **Wallet service** (operator) | Holds the sats. Each agent gets its own isolated wallet app. |
| **NWC connection** | Scoped wallet connection for that agent (pay, balance, invoice). |
| **Wallet of Agents** | Fee quotes, spend caps, rate limits, freeze, audit. MCP, HTTP and CLI tools. |

Sats are the source of truth. Optional USD figures are estimates only.

---

## 2. When to use it

| Approach | Good | Watch out |
|----------|------|-----------|
| **Your own node or self-custody wallet** | Non-custodial | More work to run. Use this if you can. |
| **Raw NWC in your agent** | Direct | Easy to leak the connection string; no fee quote, cap or freeze unless you build them. |
| **Wallet of Agents** | Quote before pay, hard spend cap, freeze, withdraw to self, MCP tools, a Lightning address without buying a domain | **Custodial:** the operator's wallet service holds the sats. Keep balances small. |

---

## 3. One-time redeem door

| Step | Action |
|------|--------|
| 1 | You receive a **private** redeem URL (`/woa/redeem?token=...`). |
| 2 | Optional: `GET` the URL. **200** `status:"ready"` means it is unclaimed. This is info only, returns no secrets and **does not burn** the token, so link previews are harmless. |
| 3 | **Claim once:** `POST /woa/redeem` with `{"token":"<hex>","cap_sats":2000,"return_address":"name@example.com"}`. `cap_sats` is optional (default 1,000, maximum 21,000). `return_address` is optional (see 4.1). Save `nwc_url`, `recovery_secret` and `lud16` to files with mode 600. |
| 4 | **Required: verify the door closed.** `GET` the same URL again and expect **410** `already_used` (or **404**). If it still says `ready`, stop and tell the operator. |
| 5 | Use the wallet through `woa mcp` or HTTP. Never paste `nwc_url`, `recovery_secret` or the redeem token into chat or public posts. |
| 6 | Lost the NWC? `POST /woa/restore` with `{"recovery_secret":"woa_rec_..."}`. Save the **new** recovery secret from the response; the old one stops working. The redeem URL stays closed. A wrong secret returns `401 {"error":"restore_failed"}`. Within 24 hours of a return-address change it returns `409 restore_locked` (see 4.1). |

A cap above the ceiling returns **400** `invalid_cap` and nothing is claimed.

---

## 4. Spend cap: you choose, we enforce

You choose your spend limit, up to 21,000 sats, and we enforce it.

- Set it when you claim (`cap_sats` on `POST /woa/redeem`), or change it any time:
  `POST /woa/cap` with `{"recovery_secret":"woa_rec_...","cap_sats":5000}`.
- Omit `cap_sats` to read your current limit, how much you have spent and what remains.
- Raise or lower any time, from 1 to 21,000. A lower limit applies to the very next payment.
- The limit is a running total of everything the wallet has spent. It does not reset.
- **Routing fees count against your limit.** Before each payment the wallet needs room for the amount plus a fee reserve (the larger of 10 sats or 1%). After the payment settles only the actual fee stays counted. So the largest single payment is a little under your remaining limit.
- This call does not rotate your recovery secret. Wrong secret: **401** `auth_failed`. Out of range: **400** `invalid_cap`.

- Every read also returns `balance_sats`, `max_spendable_now_sats` (largest payment that fits right now, fee reserve included), `low_balance` (balance under `low_balance_threshold_sats`, default 100), `blocked` (nothing can be spent), `last_topup_at` and `last_topup_sats`. Set your own threshold with `"low_balance_sats":N` (0 turns the flag off). Add `"history":true` to get `cap_history`.

## 4.0 Spend log, memo and run_id (v0.18)

- `POST /woa/pay` with `{"recovery_secret":"woa_rec_...","invoice":"lnbc...","memo":"what this paid for","run_id":"run-42"}` pays from your wallet and stores the memo and run_id with the payment. The receipt echoes them.
- `memo`: up to 140 characters; control characters are stripped; anything that looks like a secret, invoice or wallet link is refused (**400** `invalid_memo`). `run_id`: 1 to 64 of letters, digits, dot, underscore, colon, hyphen (**400** `invalid_run_id`).
- Add `"dry_run":true` to check without paying.
- A refused spend says why: **402** `spend_refused` with `reason` (`amount`, `fee_reserve`, `cap`, `ceiling`, `balance` or `frozen`), `max_spendable_now_sats`, `needed_sats`, `cap_remaining_sats` and `balance_sats`.
- Paying an invoice this wallet already paid answers **409** `already_paid` with `payment_hash`, `paid_at` and the earlier `receipt`. Nothing is paid twice, and it never looks like a routing failure (that stays **502** `payment_failed`).
- `POST /woa/ledger` with `{"recovery_secret":"woa_rec_..."}` returns `spends` (door payments with memo and run_id) and `cap_history` (every cap change: `at`, `old_cap_sats`, `new_cap_sats`, `actor`, `auth`, `via`). Both are append-only.
- Paying over NWC still works as before; those payments are not in the door log, so log them yourself with the preimage. The toolkit `woa_pay` tool also takes `memo` and `run_id` and stores them in its ledger.

---

## 4.1 Rip-cord: send it all home

One call sends the wallet's whole spendable balance to a Lightning address you chose earlier, then freezes the wallet. Use it when the agent is done, compromised, or misbehaving.

**1. Set a return address.** It must be a Lightning address that resolves to LNURL-pay (`https://<domain>/.well-known/lnurlp/<name>` with a callback). Anything else returns **400** `invalid_return_address`.

- At claim: add `"return_address":"name@example.com"` to `POST /woa/redeem`. Active immediately. If it does not resolve, nothing is claimed and the token stays usable.
- Later: `POST /woa/return-address` with `{"recovery_secret":"woa_rec_...","return_address":"name@example.com"}`. Send `null` to clear it. Omit `return_address` to read the active and pending values.

**Cooldown.** A set, change or clear made through `/woa/return-address` only takes effect for the rip-cord after 24 hours. Until then the rip-cord keeps using the current address (or answers **409** if there is none). Re-send your current address to cancel a pending change. This means someone who steals your recovery secret cannot point the rip-cord at their own address and pull it straight away.

**2. Pull it.** `POST /woa/ripcord` with `{"recovery_secret":"woa_rec_...","confirm":true}`.

- Pays the **active return address only**. A request that names any destination is refused with **400** `destination_not_allowed`.
- Sends the whole spendable balance. Routing fees come out of the wallet; if the payment needs a fee reserve (the larger of 10 sats or 1%), that much stays behind.
- `freeze` defaults to `true`: the spend limit drops to 1 sat and you can no longer change it (**409** `wallet_frozen` on `/woa/cap`). Receiving still works; pull again to sweep anything that arrives later. Send `"freeze":false` to keep the wallet usable.
- Safe to repeat: with nothing to send you get **200** `result:"noop"`.
- Errors: **400** `confirm_required`, **401** `auth_failed`, **409** `no_return_address`, **502** `ripcord_failed` (check your balance before retrying), **503** during maintenance.

**Mutual lock.** Restore replaces your recovery secret, so the two are kept apart:

- After a return-address change made with `/woa/return-address` (set, replace or clear), `POST /woa/restore` is refused for 24 hours, and for as long as a change is pending: **409** `restore_locked` with `unlock_at`. Your current secret keeps working for the rip-cord, the limit and reads.
- After a restore, return-address changes are refused for 24 hours: **409** `return_address_locked` with `unlock_at`. Reading the address still works.
- An address given at claim does not start the lock. Re-sending your current address to cancel a pending change is always allowed and does not start it either.
- Why: someone who steals your secret cannot both lock you out (by restoring) and redirect the rip-cord in the same window.

---

## 5. Fees

Wallet of Agents charges **no service fee** today.

| Direction | What you pay |
|-----------|--------------|
| **Send** (NWC, `POST /woa/pay` or the toolkit) | `0` service fee. Only the Lightning routing fee, at cost. It is shown on the receipt (`fee_sats`) and counts against your spend cap. |
| **Receive** | `0` |
| **Rip-cord and withdraw to self** | `0` service fee. Routing fees come out of the balance. |

Before each payment the wallet must have room for the amount plus a routing fee reserve (the larger of 10 sats or 1%). Once the payment settles only the actual routing fee stays counted against your cap.

The self-hosted toolkit's `woa_quote` still prints a legacy local `fee` line (the larger of 1 sat or 0.5%). Wallet of Agents does not collect it: it is only recorded in your own local ledger (`ledger_only`) unless you configure a fee sink of your own. Treat it as extra local headroom, not a charge.

---

## 6. Controls

- **Spend cap:** enforced on every payment (see section 4).
- **Rate limits:** default about 30 payments an hour per agent. Over the limit: stop, do not retry in a loop.
- **Freeze:** if balance or health reports `frozen: true`, stop and report. The operator can freeze or revoke your wallet app.
- **Withdraw to self:** `woa_withdraw_quote` then `woa_withdraw` to an invoice or Lightning address you control. Do this before trusting a large balance.
- **Audit:** `woa_audit` gives a service summary, or your own ledger with no secrets in it.

---

## 7. Lightning address

Every claimed wallet can take its own `<handle>@walletofagents.dev`:

- **Claim or change:** `POST /woa/lnaddress` with `{"recovery_secret":"woa_rec_...","handle":"my-agent"}`. Omit `handle` to read; send `"handle":null` to release (the name stays held for your wallet for 30 days).
- **Rules:** 3 to 32 characters, lowercase `a-z 0-9 . _ -`, starting and ending with a letter or digit. Some names are reserved. One handle per wallet.
- **Payers** resolve `https://walletofagents.dev/.well-known/lnurlp/<handle>` (LUD-16). Invoices commit to the address metadata and land in your wallet. 1 to 21,000 sats; comments up to 64 characters.
- `POST /woa/cap` reads also return `lightning_address`.

A wallet may also come with a `handle@getalby.com` address; both receive into the same wallet. Spend only through the tools above.

---

## 8. MCP (preferred)

```bash
cd /path/to/wallet-of-agents
npm install && npm test && npm run build
```

Register the server in your MCP client:

```json
{
  "mcpServers": {
    "wallet-of-agents": {
      "command": "node",
      "args": ["/absolute/path/to/wallet-of-agents/dist/mcp.js"],
      "env": {
        "WOA_NWC_FILE": "/absolute/path/to/your-wallet.nwc",
        "WOA_AGENT_ID": "your-agent-id",
        "WOA_AGENT_TOKEN": "your-agent-token"
      }
    }
  }
}
```

Or run `woa mcp` with the same environment.

| Tool | Purpose |
|------|---------|
| `woa_balance` | Spendable sats (plus optional USD estimate) |
| `woa_quote` | Local quote `{amount, fee, total}` before paying (the `fee` line is local headroom, see section 5) |
| `woa_invoice` | Create a receive invoice (receive fee 0) |
| `woa_pay` | Pay a BOLT-11 invoice or Lightning address |
| `woa_withdraw_quote` | Quote a full or partial withdraw |
| `woa_withdraw` | Withdraw to yourself |
| `woa_enroll` | Register an agent (prefer `clientPubkey`) |
| `woa_audit` | Service or per-agent ledger |

Suggested loop:

1. `woa_balance`: check `frozen` and how much cap remains.
2. `woa_quote` for the amount you intend to pay.
3. If the balance covers `total` and you are not frozen, `woa_pay`.
4. Return the `preimage` and the `quote`. Never invent fees.

---

## 9. HTTP (local)

```bash
export WOA_NWC_FILE=/absolute/path/to/your-wallet.nwc
export WOA_BIND=127.0.0.1
export WOA_PORT=8787
woa serve
```

| Method | Path | Notes |
|--------|------|-------|
| GET | `/health` | Freeze and readiness |
| GET | `/quote?amount=` | Fee quote |
| GET | `/balance` | Balance |
| POST | `/invoice` | `{ "amountSats", "description"? }` |
| POST | `/pay` | `{ "invoice" }` or `{ "lnaddress", "amountSats" }` |
| GET | `/withdraw/quote` | Full withdraw if amount is omitted |
| POST | `/withdraw` | Invoice or Lightning address |
| GET | `/audit` | Summary or per-agent ledger |

The default is localhost. Do not present any IP address as an official product endpoint.

---

## 10. Safety rules

| Do | Do not |
|----|--------|
| Keep wallet files at mode 600 | Paste NWC strings, recovery secrets or redeem tokens anywhere public |
| Quote before pay; respect freeze, cap and rate limits | Retry in a loop after a freeze or a cap refusal |
| Withdraw to addresses you control | Keep more in a custodial wallet than you can afford to lose |

---

## 11. Honest custody

The sats sit with the operator's wallet service, in an isolated app per agent. Wallet of Agents is a policy, fee and tooling layer over a scoped NWC connection. Whoever holds the NWC string can spend within the cap. You can withdraw to yourself at any time; the operator can freeze or revoke the wallet app. This is **custodial**, with small hard caps. If you can run your own wallet, do.

## 12. What not to claim

- Not a launched consumer product or a domain launch.
- Not an official public API at any IP address.
- Not non-custodial and not bank grade.
