# NIP-61 Nutzaps — Implementation Plan

## What is NIP-61?

NIP-61 defines **Nutzaps** — Cashu ecash-based zaps on Nostr. Instead of paying a Lightning invoice (NIP-57), the sender creates Cashu token proofs locked to the recipient's pubkey (P2PK) and publishes them as a **kind 9321** event. The recipient redeems the proofs from the mint.

### Key Differences from NIP-57 Zaps

| Aspect | NIP-57 Zaps | NIP-61 Nutzaps |
|--------|-------------|----------------|
| Payment | Lightning invoice via LNURL | Cashu proofs in Nostr event |
| Requires | Recipient has lud16/lud06 | Recipient has kind 10019 mint list |
| Event kind | 9734 request + 9735 receipt | 9321 nutzap event |
| Custody | Lightning wallet provider | Self-custodied ecash |
| Privacy | LNURL server sees payment | Mint sees redemption, not sender |
| Offline | Recipient must have LN node | Proofs wait on relays |

### NIP-61 Event Structure (kind 9321)

```json
{
  "kind": 9321,
  "content": "optional comment",
  "tags": [
    ["proof", "{...serialized proof JSON...}"],
    ["u", "https://mint.example.com"],
    ["e", "<event-id-being-zapped>"],
    ["p", "<recipient-pubkey>"],
    ["amount", "100"],
    ["unit", "sat"],
    ["alt", "This is a nutzap"]
  ]
}
```

### Kind 10019 — CashuMintList (Recipient Signal)

A replaceable event published by users who want to receive nutzaps:

```json
{
  "kind": 10019,
  "tags": [
    ["mint", "https://mint.minibits.cash/Bitcoin"],
    ["mint", "https://mint.coinos.io"],
    ["relay", "wss://relay.damus.io"],
    ["pubkey", "<optional-p2pk-pubkey>"]
  ]
}
```

## Current State of the App

### What Already Exists

1. **NIP-60 Cashu Wallet** — Full wallet on [`cashu.html`](www/cashu.html) with [`cashu-wallet.mjs`](www/js/cashu-wallet.mjs) controller. Direct cashu-ts operations in [`ndk-worker.js`](www/ndk-worker.js) — send, receive, deposit, pay invoice, proof management.

2. **NIP-57 Zap Flow** — [`zaps.mjs`](www/js/zaps.mjs) handles LNURL resolution, zap request creation (kind 9734), invoice fetching. [`post-interactions.mjs`](www/js/post-interactions.mjs) has `handleZapClick()` that prompts for amount, creates invoice, pays via `walletPayInvoice()`.

3. **NDK Bundle Includes NIP-61 Classes** — The [`ndk-entry.js`](build/ndk-entry.js) re-exports from `ndk/wallet/src` and `ndk/core/src` which include:
   - [`NDKNutzap`](ndk/core/src/events/kinds/nutzap/index.ts:16) — Kind 9321 event class with proof management, P2PK, validation
   - [`NDKCashuMintList`](ndk/core/src/events/kinds/nutzap/mint-list.ts:7) — Kind 10019 event class
   - [`NDKNutzapMonitor`](ndk/wallet/src/nutzap-monitor/index.ts:62) — Monitors incoming nutzaps and auto-redeems
   - [`NDKZapper.zapNip61()`](ndk/core/src/zapper/index.ts:352) — NIP-61 zap method in the zapper
   - Nutzap validation, proof spend state checking, grouping utilities

4. **cashu-ts Exports** — `CashuMint`, `CashuWallet`, `getDecodedToken`, `getEncodedTokenV4` are all available in the bundle.

5. **Worker Message API** — All wallet operations go through `init-ndk.mjs` → SharedWorker message protocol. The worker has direct access to cashu-ts and NDK.

### What's Missing for NIP-61

1. **Sending nutzaps** — No way to create P2PK-locked proofs and publish kind 9321 events
2. **Publishing kind 10019** — No way to advertise mint list for receiving nutzaps
3. **Receiving/redeeming nutzaps** — No nutzap monitor or redemption flow
4. **Detecting recipient nutzap support** — No check for kind 10019 before offering nutzap option
5. **UI integration** — Zap button only does NIP-57; no nutzap option in the zap flow

## Architecture

```mermaid
flowchart TD
    subgraph Sending a Nutzap
        A[User clicks Zap on a post] --> B{Recipient has kind 10019?}
        B -->|Yes| C[Show nutzap option in zap modal]
        B -->|No| D[NIP-57 Lightning zap only]
        C --> E[User picks amount]
        E --> F[Worker: create P2PK proofs via cashu-ts]
        F --> G[Worker: build and sign kind 9321 event]
        G --> H[Worker: publish to relays]
    end

    subgraph Receiving Nutzaps
        I[Worker subscription: kind 9321 where p = me] --> J[Validate nutzap]
        J --> K[Check proof spend state at mint]
        K -->|Unspent| L[Redeem proofs via cashu-ts]
        L --> M[Store proofs in wallet / kind 7375]
        M --> N[Broadcast balance update]
        K -->|Already spent| O[Mark as spent / ignore]
    end

    subgraph Publishing Mint List
        P[cashu.html Settings] --> Q[User configures mints + relays]
        Q --> R[Publish kind 10019 event]
    end
```

## Implementation Plan

### Phase 1: Publish Kind 10019 Mint List

Enable the user to advertise their nutzap preferences so others can send them nutzaps.

**Files modified:**
- [`www/ndk-worker.js`](www/ndk-worker.js) — New handler `handlePublishMintList`
- [`www/js/init-ndk.mjs`](www/js/init-ndk.mjs) — New export `walletPublishMintList()`
- [`www/js/cashu-wallet.mjs`](www/js/cashu-wallet.mjs) — New controller method `publishMintList()`
- [`www/cashu.html`](www/cashu.html) — UI toggle/button in settings section

**Worker handler logic:**
1. Build a kind 10019 event with `mint` tags from the wallet's configured mints
2. Add `relay` tags from the user's connected relays
3. Optionally add a `pubkey` tag for P2PK (the wallet's cashu pubkey)
4. Sign and publish the event

### Phase 2: Detect Recipient Nutzap Support

Before sending a nutzap, check if the recipient has published a kind 10019 event.

**Files modified:**
- [`www/ndk-worker.js`](www/ndk-worker.js) — New handler `handleFetchMintList`
- [`www/js/init-ndk.mjs`](www/js/init-ndk.mjs) — New export `fetchRecipientMintList(pubkey)`
- [`www/js/zaps.mjs`](www/js/zaps.mjs) — New function `resolveNutzapSpecForPubkey()`

**Worker handler logic:**
1. Fetch kind 10019 event for the given pubkey
2. Parse mint URLs, relay URLs, and optional P2PK pubkey
3. Return `{ mints, relays, p2pk }` or null if no mint list found

### Phase 3: Send Nutzaps (Kind 9321)

Create P2PK-locked Cashu proofs and publish them as a nutzap event.

**Files modified:**
- [`www/ndk-worker.js`](www/ndk-worker.js) — New handler `handleWalletSendNutzap`
- [`www/js/init-ndk.mjs`](www/js/init-ndk.mjs) — New export `walletSendNutzap()`
- [`www/js/zaps.mjs`](www/js/zaps.mjs) — New function `sendNutzap()`

**Worker handler logic:**
1. Receive: `amount`, `recipientPubkey`, `recipientMints`, `recipientP2pk`, `eventId` (optional), `comment`
2. Find a mint in common between sender wallet and recipient mint list
3. If no common mint, attempt cross-mint transfer (send from our mint, swap into recipient mint)
4. Create P2PK-locked proofs using cashu-ts `wallet.send()` with P2PK spending conditions
5. Build proof tags with replay protection (`e` tag for event ID, `P` tag for sender pubkey in proof secret)
6. Construct kind 9321 event with: `proof` tags, `u` (mint), `p` (recipient), `e` (event), `amount`, `unit`, `alt`, `comment` tag
7. Sign via MessageBasedSigner and publish
8. Update local proof store (remove spent proofs)
9. Return nutzap event details and updated balance

**P2PK proof creation (cashu-ts):**
```javascript
const { keep, send } = await cashuWallet.send(amount, proofs, {
    pubkey: recipientP2pk,  // Lock to recipient
    tags: [
        ['e', eventId],           // Replay protection
        ['P', senderPubkey]       // Sender verification
    ]
});
```

### Phase 4: Receive and Redeem Nutzaps

Monitor for incoming nutzaps and automatically redeem them.

**Files modified:**
- [`www/ndk-worker.js`](www/ndk-worker.js) — New subscription for kind 9321, new handler `handleRedeemNutzap`, nutzap processing logic
- [`www/js/init-ndk.mjs`](www/js/init-ndk.mjs) — New events `ndkNutzapReceived`, new export `walletRedeemNutzap()`
- [`www/js/cashu-wallet.mjs`](www/js/cashu-wallet.mjs) — Nutzap event listener, redemption status display

**Worker logic:**
1. At startup (after wallet hydration), subscribe to `{ kinds: [9321], '#p': [currentPubkey] }`
2. On new event: parse as NDKNutzap, validate (check proofs exist, single mint, single recipient)
3. Check proof spend state at the mint via `cashuWallet.checkProofsStates()`
4. If unspent: redeem P2PK proofs using the wallet's private key
5. Add redeemed proofs to `directProofStore`, publish updated kind 7375 events
6. Broadcast `nutzapReceived` message with amount, sender, comment
7. Broadcast updated balance
8. Track redeemed nutzap IDs to avoid double-processing

**P2PK redemption (cashu-ts):**
```javascript
const proofs = nutzap.proofs;
const redeemed = await cashuWallet.receive(proofs, {
    privkey: walletPrivateKey  // Needed to unlock P2PK proofs
});
```

**Key consideration:** P2PK redemption requires a **private key**. The current wallet uses `window.nostr` for signing (NIP-07/NIP-46), which doesn't expose the raw private key. Options:
- **Option A:** Generate a separate Cashu-specific keypair stored encrypted in kind 17375 wallet metadata. The `pubkey` tag in kind 10019 advertises this key. This is the approach NDK's `NDKCashuMintList.p2pk` supports.
- **Option B:** Use the nostr private key directly (only works with NIP-07 extensions that expose it, which most don't).
- **Option A is recommended** — it's what the NIP-61 spec and NDK implementation expect.

### Phase 5: UI Integration — Zap Modal Enhancement

Update the zap flow to offer nutzap as an option when the recipient supports it.

**Files modified:**
- [`www/js/zaps.mjs`](www/js/zaps.mjs) — Update `promptZapDetails()` to include nutzap toggle
- [`www/js/post-interactions.mjs`](www/js/post-interactions.mjs) — Update `handleZapClick()` to check for nutzap support and route accordingly
- [`www/css/client.css`](www/css/client.css) — Nutzap-specific styling (if needed beyond inline styles)

**Flow change in `handleZapClick()`:**
1. Before showing the zap modal, fetch recipient's kind 10019 mint list
2. If mint list exists AND user has a Cashu wallet with balance:
   - Show "Nutzap" toggle/option in the zap details modal
   - Default to nutzap if both parties share a mint
3. If nutzap selected: call `walletSendNutzap()` instead of the NIP-57 flow
4. If NIP-57 selected (or no mint list): existing Lightning zap flow

### Phase 6: Display Nutzaps in Post Interactions

Show received nutzaps alongside regular zaps in the post interaction counts.

**Files modified:**
- [`www/js/post-interactions.mjs`](www/js/post-interactions.mjs) — Subscribe to kind 9321 events, parse nutzap amounts, include in zap totals

**Changes:**
1. Add kind 9321 to the subscription filter alongside kind 9735
2. When a kind 9321 event arrives, parse it as a nutzap: extract amount from proof tags, sender from pubkey, comment from content
3. Add to the same `state.zaps` array (or a parallel `state.nutzaps` array) and include in `zapTotal`
4. Display nutzap indicator (e.g., different icon or label) to distinguish from Lightning zaps

### Phase 7: Cashu Keypair Management

Generate and store a Cashu-specific P2PK keypair for nutzap reception.

**Files modified:**
- [`www/ndk-worker.js`](www/ndk-worker.js) — Keypair generation, encrypted storage in kind 17375
- [`www/js/cashu-wallet.mjs`](www/js/cashu-wallet.mjs) — Expose P2PK pubkey for kind 10019

**Logic:**
1. When wallet is created or first nutzap setup: generate a secp256k1 keypair
2. Store the private key encrypted (NIP-44) in the kind 17375 wallet event content
3. The public key (with "02" prefix for Cashu) goes in the kind 10019 `pubkey` tag
4. On wallet load: decrypt and cache the private key for nutzap redemption
5. The `NDKNutzapMonitor` accepts private keys via `addPrivkey()` — we feed it this key

## Technical Considerations

### Mint Compatibility
- Sender and recipient should share at least one mint for direct nutzaps
- If no common mint: cross-mint transfer is possible but adds complexity and fees
- The UI should indicate when mints are compatible vs. when a swap is needed

### Proof Replay Protection
- Each proof's secret should contain an `e` tag matching the event being zapped
- This prevents the same proof from being used in multiple nutzap events
- The `P` tag in the proof secret identifies the sender for verification

### Privacy Benefits
- The mint only sees proof redemption, not the Nostr event or social context
- No LNURL server involved — no third party sees the payment
- Sender and recipient interact only through Nostr relays and the Cashu mint

### Error Handling
- Mint offline: queue nutzap for retry, show pending state
- Proofs already spent: mark nutzap as invalid, notify user
- No common mint: offer to swap or fall back to NIP-57
- Insufficient balance: show balance and suggest deposit

## Files Summary

| File | Changes |
|------|---------|
| [`www/ndk-worker.js`](www/ndk-worker.js) | `handlePublishMintList`, `handleFetchMintList`, `handleWalletSendNutzap`, `handleRedeemNutzap`, nutzap subscription, P2PK keypair management |
| [`www/js/init-ndk.mjs`](www/js/init-ndk.mjs) | `walletPublishMintList()`, `fetchRecipientMintList()`, `walletSendNutzap()`, `walletRedeemNutzap()`, `ndkNutzapReceived` event |
| [`www/js/zaps.mjs`](www/js/zaps.mjs) | `resolveNutzapSpecForPubkey()`, `sendNutzap()`, update `promptZapDetails()` with nutzap toggle |
| [`www/js/cashu-wallet.mjs`](www/js/cashu-wallet.mjs) | `publishMintList()`, nutzap event listeners, P2PK pubkey exposure |
| [`www/js/post-interactions.mjs`](www/js/post-interactions.mjs) | Subscribe to kind 9321, parse nutzap amounts, update zap totals, nutzap routing in `handleZapClick()` |
| [`www/cashu.html`](www/cashu.html) | Mint list publish button, nutzap reception settings, incoming nutzap display |
| [`build/ndk-entry.js`](build/ndk-entry.js) | Verify NDKNutzap, NDKCashuMintList exports (likely already included via wildcard) |

## Dependency Chain

```mermaid
flowchart LR
    P7[Phase 7: Cashu Keypair] --> P1[Phase 1: Publish 10019]
    P1 --> P4[Phase 4: Receive Nutzaps]
    P2[Phase 2: Detect Support] --> P3[Phase 3: Send Nutzaps]
    P3 --> P5[Phase 5: UI Integration]
    P4 --> P6[Phase 6: Display in Posts]
    P5 --> P6
```

Phases 1-2 and Phase 7 can be worked on in parallel. Phase 3 depends on Phase 2. Phase 4 depends on Phase 1 and 7. Phases 5-6 depend on everything else.
