# NIP-60 Cashu Wallet for Didactyl Agents ## Overview Add a NIP-60 Cashu wallet to every Didactyl agent. The wallet stores its state on Nostr relays (encrypted with the agent's keys) and interacts with Cashu mints over HTTP. The agent manages the wallet autonomously — it can check balances, mint tokens (fund via Lightning), melt tokens (pay Lightning invoices), swap tokens, and persist all state transitions as Nostr events per the NIP-60 spec. ## Available `nostr_core_lib` Primitives ### NIP-60 Event Layer (`nip060.h`) | Function | Purpose | |----------|---------| | `nostr_nip60_create_wallet_event()` | Create kind:17375 wallet event (privkey + mint URLs, NIP-44 encrypted) | | `nostr_nip60_parse_wallet_event()` | Decrypt and parse wallet event | | `nostr_nip60_free_wallet_data()` | Free wallet data | | `nostr_nip60_create_token_event()` | Create kind:7375 token event (proofs, NIP-44 encrypted) | | `nostr_nip60_parse_token_event()` | Decrypt and parse token event | | `nostr_nip60_free_token_data()` | Free token data | | `nostr_nip60_create_token_deletion()` | Create kind:5 deletion for spent tokens | | `nostr_nip60_create_rollover_token()` | Create new token with remaining proofs + del references | | `nostr_nip60_create_history_event()` | Create kind:7376 spending history event | | `nostr_nip60_parse_history_event()` | Decrypt and parse history event | | `nostr_nip60_free_history_data()` | Free history data | | `nostr_nip60_create_quote_event()` | Create kind:7374 quote tracking event | | `nostr_nip60_parse_quote_event()` | Decrypt and parse quote event | | `nostr_nip60_sum_proofs()` | Sum proof amounts | | `nostr_nip60_proofs_to_json()` / `nostr_nip60_proofs_from_json()` | Proof serialization | | `nostr_nip60_create_wallet_filter()` | Build REQ filter for kinds 17375+7375 | | `nostr_nip60_create_history_filter()` | Build REQ filter for kind 7376 | ### Cashu Mint HTTP Client (`cashu_mint.h`) | Function | Purpose | |----------|---------| | `cashu_mint_get_info()` | GET /v1/info — mint name, pubkey, keysets | | `cashu_mint_request_mint_quote()` | POST /v1/mint/quote/bolt11 — get Lightning invoice to fund wallet | | `cashu_mint_check_mint_quote()` | GET /v1/mint/quote/bolt11/{id} — check if invoice paid | | `cashu_mint_mint_tokens()` | POST /v1/mint/bolt11 — claim tokens after invoice paid | | `cashu_mint_request_melt_quote()` | POST /v1/melt/quote/bolt11 — get quote to pay Lightning invoice | | `cashu_mint_check_melt_quote()` | GET /v1/melt/quote/bolt11/{id} — check melt status | | `cashu_mint_melt_tokens()` | POST /v1/melt/bolt11 — pay Lightning invoice with tokens | | `cashu_mint_swap()` | POST /v1/swap — swap proofs (split/merge denominations) | | `cashu_mint_check_proofs_state()` | POST /v1/checkstate — verify proof spendability | ## Architecture ```mermaid graph TD A[Agent Main Loop] --> B[Cashu Wallet Manager] B --> C[In-Memory Wallet State] C --> D[nostr_core_lib NIP-60 Events] C --> E[nostr_core_lib Cashu Mint HTTP] D --> F[Nostr Relays] E --> G[Cashu Mint Servers] H[LLM Tool Calls] --> I[cashu_wallet_balance] H --> J[cashu_wallet_mint_quote] H --> K[cashu_wallet_mint_claim] H --> L[cashu_wallet_melt_quote] H --> M[cashu_wallet_melt_pay] H --> N[cashu_wallet_info] H --> O[cashu_wallet_check_proofs] I --> B J --> B K --> B L --> B M --> B N --> B O --> B ``` ### Module Boundaries #### 1. `src/cashu_wallet.h` / `src/cashu_wallet.c` — Cashu Wallet Manager The core wallet module that owns in-memory state and orchestrates all operations. **State:** - Parsed `nostr_nip60_wallet_data_t` (privkey, mint URLs) - Array of `nostr_nip60_token_data_t` with their event IDs (unspent proofs) - Wallet initialized flag - Mutex for thread safety **Lifecycle:** - `cashu_wallet_init(config)` — called during agent startup - `cashu_wallet_load_from_relays()` — fetch kind:17375 + kind:7375 events, decrypt, populate state - `cashu_wallet_create_new(mint_urls, mint_count)` — generate wallet privkey, publish kind:17375 - `cashu_wallet_cleanup()` — free all state **Operations:** - `cashu_wallet_balance()` — sum all unspent proofs, grouped by mint - `cashu_wallet_balance_total()` — total across all mints - `cashu_wallet_mint_quote(mint_url, amount, unit)` — request Lightning invoice from mint - `cashu_wallet_check_mint_quote(mint_url, quote_id)` — poll quote status - `cashu_wallet_claim_tokens(mint_url, quote_id, amount)` — mint tokens after invoice paid, publish kind:7375 - `cashu_wallet_melt_quote(mint_url, payment_request, unit)` — get quote to pay LN invoice - `cashu_wallet_melt_pay(mint_url, quote_id, proofs)` — pay LN invoice, delete spent tokens, rollover change - `cashu_wallet_swap(mint_url, proofs, target_amounts)` — split/merge denominations - `cashu_wallet_check_proofs(mint_url)` — validate proof spendability, clean up spent - `cashu_wallet_get_info(mint_url)` — fetch mint info **State Persistence (Nostr events):** - After every balance-changing operation, publish updated kind:7375 token events - Delete old kind:7375 events via kind:5 when proofs are spent - Publish kind:7376 history events for audit trail - Optionally publish kind:7374 quote events for cross-device state #### 2. `src/tools/tool_cashu_wallet.c` — LLM Tool Interface Exposes wallet operations as tools the LLM can call. #### 3. Config additions in `src/config.h` / `src/config.c` New `cashu_wallet_config_t` section. ## Config Schema Changes Add to `didactyl_config_t`: ```c typedef struct { int enabled; // master switch char** mint_urls; // default mint URLs int mint_count; char unit[16]; // default unit, e.g. "sat" int auto_load; // load wallet from relays on startup int mint_timeout_seconds; // HTTP timeout for mint operations } cashu_wallet_config_t; ``` Example `config.jsonc`: ```jsonc "cashu_wallet": { "enabled": true, "mints": [ "https://mint.minibits.cash", "https://stablenut.umint.cash" ], "unit": "sat", "auto_load": true, "mint_timeout_seconds": 30 } ``` ## Tool Definitions ### `cashu_wallet_balance` - **Description:** Check the agent wallet balance across all mints - **Parameters:** none (or optional `mint_url` to filter) - **Returns:** `{ "total_sats": 1234, "by_mint": [{ "mint": "...", "balance": 500 }] }` ### `cashu_wallet_info` - **Description:** Get information about a Cashu mint - **Parameters:** `{ "mint_url": "https://..." }` - **Returns:** `{ "name": "...", "version": "...", "keysets": [...] }` ### `cashu_wallet_mint_quote` - **Description:** Request a Lightning invoice to fund the wallet - **Parameters:** `{ "mint_url": "https://...", "amount": 1000, "unit": "sat" }` - **Returns:** `{ "quote_id": "...", "payment_request": "lnbc...", "expiry": ... }` ### `cashu_wallet_mint_check` - **Description:** Check if a mint quote invoice has been paid - **Parameters:** `{ "mint_url": "https://...", "quote_id": "..." }` - **Returns:** `{ "paid": true/false, "amount": 1000 }` ### `cashu_wallet_mint_claim` - **Description:** Claim tokens after a mint quote invoice is paid - **Parameters:** `{ "mint_url": "https://...", "quote_id": "...", "amount": 1000 }` - **Returns:** `{ "success": true, "new_balance": 2234 }` ### `cashu_wallet_melt_quote` - **Description:** Get a quote to pay a Lightning invoice from the wallet - **Parameters:** `{ "mint_url": "https://...", "payment_request": "lnbc...", "unit": "sat" }` - **Returns:** `{ "quote_id": "...", "amount": 500, "fee_reserve": 2 }` ### `cashu_wallet_melt_pay` - **Description:** Pay a Lightning invoice using wallet tokens - **Parameters:** `{ "mint_url": "https://...", "quote_id": "..." }` - **Returns:** `{ "success": true, "paid": true, "new_balance": 1732 }` ### `cashu_wallet_check_proofs` - **Description:** Validate that stored proofs are still spendable - **Parameters:** `{ "mint_url": "https://..." }` (optional, checks all mints if omitted) - **Returns:** `{ "valid": 15, "spent": 2, "cleaned": true }` ## Phased Implementation Plan ### Phase 1: Cashu Wallet Core Module - [ ] Create `src/cashu_wallet.h` with wallet state structs and function declarations - [ ] Create `src/cashu_wallet.c` with: - [ ] `cashu_wallet_init()` / `cashu_wallet_cleanup()` lifecycle - [ ] In-memory state: wallet data + token array with event IDs - [ ] Mutex protection for thread safety - [ ] `cashu_wallet_create_new()` — generate random privkey, build `nostr_nip60_wallet_data_t`, publish kind:17375 via `nostr_handler_publish_kind_event()` - [ ] `cashu_wallet_load_from_relays()` — use `nostr_nip60_create_wallet_filter()` + `nostr_handler_query_json()` to fetch and parse wallet + token events - [ ] `cashu_wallet_balance()` / `cashu_wallet_balance_total()` — iterate in-memory tokens, call `nostr_nip60_sum_proofs()` ### Phase 2: Mint Interaction (Fund & Pay) - [ ] Add to `src/cashu_wallet.c`: - [ ] `cashu_wallet_mint_quote()` — call `cashu_mint_request_mint_quote()` - [ ] `cashu_wallet_check_mint_quote()` — call `cashu_mint_check_mint_quote()` - [ ] `cashu_wallet_claim_tokens()` — call `cashu_mint_mint_tokens()`, parse response proofs via `nostr_nip60_proofs_from_json()`, create and publish kind:7375 token event, publish kind:7376 history - [ ] `cashu_wallet_melt_quote()` — call `cashu_mint_request_melt_quote()` - [ ] `cashu_wallet_melt_pay()` — select proofs to spend, call `cashu_mint_melt_tokens()`, delete spent token events via `nostr_nip60_create_token_deletion()`, rollover change via `nostr_nip60_create_rollover_token()`, publish kind:7376 history - [ ] `cashu_wallet_swap()` — call `cashu_mint_swap()`, update token events - [ ] `cashu_wallet_check_proofs()` — call `cashu_mint_check_proofs_state()`, clean up spent proofs - [ ] `cashu_wallet_get_info()` — call `cashu_mint_get_info()` ### Phase 3: Config & Startup Integration - [ ] Add `cashu_wallet_config_t` to `src/config.h` - [ ] Parse `"cashu_wallet"` section in `src/config.c` - [ ] Call `cashu_wallet_init()` from `src/main.c` after `agent_init()` - [ ] If `auto_load` is true, call `cashu_wallet_load_from_relays()` during startup - [ ] If no wallet event found and mints configured, call `cashu_wallet_create_new()` - [ ] Call `cashu_wallet_cleanup()` in shutdown path - [ ] Add `src/cashu_wallet.c` to `Makefile` SRCS ### Phase 4: LLM Tools - [ ] Create `src/tools/tool_cashu_wallet.c` implementing all 8 tool execute functions - [ ] Add tool declarations to `src/tools/tools_internal.h` - [ ] Register tools in `src/tools/tools_dispatch.c` - [ ] Add JSON schemas in `src/tools/tools_schema.c` - [ ] Add `src/tools/tool_cashu_wallet.c` to `Makefile` SRCS ### Phase 5: Testing & Validation - [ ] Unit test: create wallet, publish, reload from relay, verify state - [ ] Integration test: mint quote → claim → check balance → melt quote → pay - [ ] Test proof rollover: spend partial proofs, verify deletion + new token event - [ ] Test startup: fresh agent creates wallet; existing agent loads wallet - [ ] Test error handling: mint offline, invalid proofs, insufficient balance ## Key Design Decisions 1. **Wallet privkey is NOT the agent's Nostr privkey** — per NIP-60 spec, a separate privkey is generated and stored encrypted in the kind:17375 event. This privkey is used for P2PK ecash locking (future NIP-61 support). 2. **State lives on relays** — the in-memory state is a cache. On startup the agent fetches its wallet state from relays. After every mutation, updated events are published back. 3. **Proof selection for spending** — when melting, the wallet needs to select proofs that sum to at least the required amount. Implement a simple greedy algorithm (largest-first) with change output via swap. 4. **Thread safety** — wallet operations are called from the LLM tool loop which may run concurrently with triggered skills. A mutex protects all state mutations. 5. **No NIP-61 (nutzaps) in this phase** — receiving/sending nutzaps is a separate feature that builds on top of this wallet foundation. ## File Changes Summary | File | Change | |------|--------| | `src/cashu_wallet.h` | **NEW** — cashu wallet manager header | | `src/cashu_wallet.c` | **NEW** — cashu wallet manager implementation | | `src/tools/tool_cashu_wallet.c` | **NEW** — LLM tool implementations | | `src/config.h` | Add `cashu_wallet_config_t` to `didactyl_config_t` | | `src/config.c` | Parse `"cashu_wallet"` config section | | `src/main.c` | Call `cashu_wallet_init()` / `cashu_wallet_cleanup()` | | `src/tools/tools_internal.h` | Declare `execute_cashu_wallet_*` functions | | `src/tools/tools_dispatch.c` | Register cashu wallet tool dispatch entries | | `src/tools/tools_schema.c` | Add cashu wallet tool JSON schemas | | `Makefile` | Add `cashu_wallet.c` and `tool_cashu_wallet.c` to SRCS |