Phase 1 (feed2.html):
- Removed inline comment rendering and Comments toggle button
- Comment button now opens post-feed.html?event=<eventId> in new tab
- Removed inline reply composer (kept quote composer)
- Fixed missing queryCache dependency in initPostCards
- Fixed hardcoded color:red -> var(--primary-color)
Phase 2 (post-feed.html):
- New post thread page showing post + all replies + inline composer
- Parses ?event=<hex|nevent|note> URL parameter
- Fetches replies via { kinds: [1], '#e': [postId] }
- Real-time subscriptions for new replies
- Comment button on replies opens sub-threads in new tabs
- Error states for missing/invalid/not-found posts
- All CSS uses variables, no hardcoded colors
8.4 KiB
Cashu Wallet Enhancements — Implementation Plan
Overview
Enhance the existing www/cashu.html page and zap flow, centered on Cashu as the primary (and only) payment rail. The core functionality is already implemented — this plan adds UI improvements to show zap capabilities in the feed and let users choose their zap rail.
Design philosophy: Cashu-only
The project uses Cashu for everything — no NWC, CLINK, onchain, or Lightning channels to maintain:
| Payment type | How it works | Already implemented? |
|---|---|---|
| Nutzap (NIP-61) | Send ecash proofs locked to recipient's p2pk via kind 9321 | ✅ Yes — walletSendNutzap |
| Lightning zap (NIP-57) | Melt Cashu ecash at a mint to pay a BOLT11 invoice | ✅ Yes — walletPayInvoice |
| P2P ecash transfer | Send cashuB token directly to another user | ✅ Yes — walletSendToken |
| Lightning deposit | Pay a BOLT11 invoice, mint issues ecash proofs | ✅ Yes — walletCreateDeposit |
| Lightning withdrawal | Melt ecash to pay an external BOLT11 invoice | ✅ Yes — walletPayInvoice |
Phase 1: NIP-60 Compliance Fix (DONE ✅)
Already completed and deployed (v0.7.19–v0.7.24):
- Fixed kind 17375 content format from proprietary
{mints, nutzap}object to NIP-60 tag-array[["mint",url],["privkey",hex]] - Removed non-standard
pubkeytag from 17375 public tags - Added
parseWalletContent()backwards-compat shim for reading both formats - Added auto-migration for legacy 17375 events on startup
- Added "Republish Wallet (kind 17375)" button with lightweight 17375-only republish
- Added
ensureDirectNutzapP2pk()call inpublishDirectProofsso privkey is always included
Phase 2: Zap Capability Indicators in the Feed
Show zap capability badges on each post's interaction bar so users can see at a glance whether the recipient can receive nutzaps, Lightning zaps, or both.
Current state
The interaction bar in www/js/post-interactions.mjs already renders:
- ⚡ Zap button with count
- 🥜 Nutzap count display (separate element showing total nutzaps received)
What to add
Small capability icons/badges next to the zap button showing what the recipient can receive:
┌──────────────────────────────────────────────────┐
│ ⚡ 5 🥜 2 🥜✅ ⚡✅ 💬 3 🔄 1 2h ago │
│ └─ zap ─┘ └─ nutzap ─┘ └─ capability badges ─┘ │
└──────────────────────────────────────────────────┘
Or a more compact design — color the zap icon differently based on capabilities:
| Visual | Meaning |
|---|---|
| ⚡ (default color) | Recipient can receive Lightning zaps (has lud16) |
| ⚡ + 🥜 (nut badge) | Recipient can also receive nutzaps (has kind 10019 with shared mint) |
| ⚡ (dimmed) | Recipient cannot receive any zaps (no lud16, no 10019) |
Implementation
-
2.1 Create a
resolveZapCapabilities(pubkey)function that checks:- Does the recipient have a lud16 (Lightning address)? → can receive Lightning zaps
- Does the recipient have a kind 10019 (nutzap mint list)? → can receive nutzaps
- If yes to 10019, which mints? Do any overlap with the sender's wallet mints?
-
2.2 Cache the results per pubkey (the kind 10019 and lud16 don't change often). Use a simple in-memory Map with TTL.
-
2.3 Update
createInteractionItemfor the zap type to show capability indicators:- Add a small 🥜 badge on the zap button if the recipient can receive nutzaps
- Add a tooltip showing the shared mints
- Dim the zap button if the recipient can't receive any zaps
-
2.4 Fetch kind 10019 events for post authors as the feed loads. The worker already fetches kind 10019 for nutzap sending — extend this to cache the results for feed display. Use the existing
walletFetchMintListworker function. -
2.5 Show shared mint info on hover/tooltip: "Can nutzap via mint.minibits.cash/Bitcoin" or "No shared mints — Lightning only" or "No zap capability"
Where the code lives
| File | What to change |
|---|---|
www/js/post-interactions.mjs |
Add capability badges to zap button rendering |
www/js/zaps.mjs |
Add resolveZapCapabilities() function (or extend existing resolveZapSpecForPubkey) |
www/ndk-worker.js |
May need a lightweight "fetch 10019 for display" message (or reuse walletFetchMintList) |
Phase 3: Zap Rail Selector in the Zap Dialog
When the user taps the zap button, show which rails are available and let them choose.
flowchart TD
A[User taps zap button] --> B[Show zap dialog with amount input]
B --> C{What rails are available?}
C -->|Nutzap + Lightning| D[Show rail toggle: 🥜 Nutzap | ⚡ Lightning]
C -->|Lightning only| E[Show Lightning only]
C -->|Nutzap only| F[Show Nutzap only]
C -->|Neither| G[Show error: recipient cannot receive zaps]
D --> H[User selects rail + amount]
H --> I{Rail selected}
I -->|Nutzap| J[Send NIP-61 nutzap via walletSendNutzap]
I -->|Lightning| K[Create LN invoice, melt Cashu via walletPayInvoice]
E --> K
F --> J
-
3.1 Update
promptZapDetailsinzaps.mjsto accept and display rail options:- If nutzap is available (recipient has 10019 with shared mint) → show "🥜 Nutzap" option
- If Lightning is available (recipient has lud16) → show "⚡ Lightning" option
- Let the user pick which rail to use via a toggle/selector
-
3.2 Show balance info in the dialog:
- "Cashu balance: 261 sats"
- If amount > balance → show warning "Insufficient balance"
-
3.3 Default rail selection:
- If nutzap is available and amount < 1000 sats → default to nutzap (lower fees)
- If nutzap is available and amount ≥ 1000 sats → default to Lightning (more reliable for larger amounts)
- If nutzap is not available → default to Lightning
-
3.4 Pass the selected rail back to
handleZapClickinpost-interactions.mjsso it uses the right send function.
Phase 4: cashu.html UI Polish
Minor UI improvements to the existing cashu.html page:
-
4.1 Consider renaming the page title from "CASHU" to "WALLET" since it's the project's unified wallet.
-
4.2 Update the sidenav entry in
index.htmlfrom "CASHU" to "WALLET" (keeping the samecashu.htmlURL). -
4.3 Add a "Zap via Lightning" info note in the Withdraw panel explaining that this melts ecash to pay a Lightning invoice — same mechanism used for Lightning zaps.
NIP Compliance Reference
| Rail | NIP | Kind(s) | Status |
|---|---|---|---|
| Cashu Wallet | NIP-60 | 17375, 7375, 7376, 375 | ✅ Fixed and working |
| Cashu Nutzap | NIP-61 | 10019, 9321 | ✅ Working |
| Lightning Zap | NIP-57 | 9734, 9735 | ✅ Working (via Cashu melt) |
Key Files
Files to modify
| File | Changes |
|---|---|
www/js/post-interactions.mjs |
Add zap capability badges to interaction bar, pass selected rail to zap handler |
www/js/zaps.mjs |
Add resolveZapCapabilities(), update promptZapDetails with rail selector |
www/cashu.html |
Minor UI polish (title, info notes) |
www/index.html |
Sidenav label: "CASHU" → "WALLET" |
Files reused as-is
| File | Why no changes needed |
|---|---|
www/js/cashu-wallet.mjs |
Controller already wraps all worker functions |
www/ndk-worker.js |
All wallet functions already exist (payInvoice, sendNutzap, fetchMintList) |
www/js/init-ndk.mjs |
All wallet exports already exist |
Implementation Order
- Phase 1 (DONE) — NIP-60 compliance fix
- Phase 2 (zap capability indicators) — show what the recipient can receive in the feed
- Phase 3 (rail selector) — let users choose nutzap vs Lightning melt in the zap dialog
- Phase 4 (UI polish) — minor label/info changes
No new files needed. No new infrastructure. Just UI improvements on top of the existing working zap flow.