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

**Public base:** https://138.68.188.160
**Discover:** https://138.68.188.160/woa/discover
**llms.txt:** https://138.68.188.160/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://138.68.188.160/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}`.

---

# 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`.

---

## 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

| Direction | Fee |
|-----------|-----|
| **Send** (through the Wallet of Agents tools) | `max(1 sat, ceil(amount_sats x 0.5%))` |
| **Receive** | `0` |
| **Withdraw to self** | Same as send. Drain quotes may keep a small routing reserve (about 1%). |

Examples: 100 sats costs 1 sat in fees (total 101). 1,000 sats costs 5 sats (total 1,005).
Always call the quote tool first and treat `quote.total` as what you spend. Lightning routing fees are reported separately.

---

## 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

Each redeemed wallet can come with a `handle@getalby.com` Lightning address for receiving. Publish that for incoming payments; 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` | Send fee `{amount, fee, total}`, always before paying |
| `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.
