Summary
Add support for NWC (Nostr Wallet Connect) as an alternative wallet backend, allowing users to connect any Lightning wallet via the NIP-47 protocol instead of requiring cocod + Cashu/ecash.
Motivation
Currently, routstrd exclusively relies on cocod as its wallet backend for Cashu token management and Lightning operations. This means users must use Cashu/ecash to fund and operate routstrd. Adding NWC support would:
- Lower the barrier to entry — users with existing Lightning wallets (Alby, Mutiny, Phoenix, Zeus, etc.) can connect directly without needing to understand or hold Cashu tokens
- Preserve user choice — some users prefer direct Lightning payments over ecash due to custody preferences
- Leverage existing Nostr infrastructure — routstrd already depends on
nostr-tools, applesauce-core, and applesauce-relay, so the Nostr stack is already in place
- Improve UX — NWC pairing is a one-time connection (scan a QR / paste a URI), after which the wallet handles approval flows automatically
Background: What is NWC?
NWC (NIP-47) is a Final-status Nostr Improvement Proposal that defines an E2E-encrypted protocol for apps to interact with a remote Lightning wallet through Nostr relays:
- Kind
13194 — Wallet service info event (advertises supported commands)
- Kind
23194 — Client request
- Kind
23195 — Wallet service response
- Kind
23197 — Wallet notification
- Communication uses encrypted direct messages over a Nostr relay
- Connection is established via a
nostr+wallet:// URI containing relay URL, wallet pubkey, and client secret
Key NWC commands relevant to routstrd:
| NWC Method |
routstrd Equivalent |
Description |
pay_invoice |
sendBolt11() |
Pay a BOLT11 invoice |
make_invoice |
receiveBolt11() |
Create a BOLT11 invoice to receive sats |
get_balance |
getBalances() |
Check wallet balance |
get_info |
getStatus() |
Get wallet capabilities & info |
Proposed Implementation
1. NWC Wallet Client (src/daemon/wallet/nwc-client.ts)
Create an NwcClient that implements a compatible interface with the existing CocodClient:
export interface NwcClient {
connect(uri: string): Promise<void>; // nostr+wallet://... URI
disconnect(): Promise<void>;
getBalance(): Promise<number>;
makeInvoice(amount: number): Promise<string>; // returns BOLT11 invoice
payInvoice(invoice: string): Promise<string>; // pays BOLT11, returns preimage
getInfo(): Promise<NwcWalletInfo>;
}
Implementation would use nostr-tools (already a dependency) for:
- NIP-04/NIP-44 encrypted DM handling
- Event creation (kind 23194) and subscription (kind 23195) over a Nostr relay
- Connection URI parsing (
nostr+wallet://)
2. NWC Wallet Adapter (src/daemon/wallet/nwc-adapter.ts)
Create a wallet adapter (parallel to the existing cocod-based adapter in src/daemon/wallet/index.ts) that wraps NwcClient and conforms to the same interface consumed by the daemon's HTTP routes:
export async function createNwcWalletAdapter(nwcUri: string) {
// Wraps NwcClient, maps NWC commands to the wallet adapter interface
// getBalances() → NWC get_balance
// sendBolt11() → NWC pay_invoice
// receiveBolt11()→ NWC make_invoice
// Note: Cashu operations (sendCashu/receiveCashu) would throw unsupported
}
3. Wallet Backend Selection
Update the daemon initialization to support selecting a wallet backend:
- Config: Add
walletBackend: "cocod" | "nwc" and nwcUri: string | null to RoutstrdConfig
- CLI: Add
routstrd wallet connect-nwc <uri> command to configure the NWC connection
- Onboard flow: Offer NWC as an alternative during
routstrd onboard ("Connect via Lightning wallet (NWC)" vs "Use local Cashu wallet (cocod)")
- Fallback:
cocod remains the default; NWC is opt-in
4. CLI Commands
routstrd wallet connect-nwc <uri> # Configure NWC connection
routstrd wallet disconnect-nwc # Remove NWC connection, revert to cocod
routstrd wallet nwc-status # Show NWC connection & wallet info
When NWC is the active backend, routstrd receive <amount> and routstrd send <invoice> would route through NWC instead of cocod.
5. Daemon HTTP API Updates
The daemon's existing HTTP routes (/wallet/send/bolt11, /wallet/receive/bolt11, /balance, etc.) should transparently route to whichever wallet backend is active. Cashu-specific routes (/wallet/send/cashu, /wallet/receive/cashu) would return a 501 Not Supported error when NWC is the active backend.
Implementation Notes
- No new Nostr dependencies needed —
nostr-tools, applesauce-core, and applesauce-relay are already in package.json
- NIP-44 vs NIP-04 encryption — NIP-47 specifies NIP-44 for encryption. Some wallets still use NIP-04. The implementation should support both, preferring NIP-44 and falling back to NIP-04 based on the wallet's info event
- Connection lifecycle — NWC connections are persistent (subscription to kind 23195 events). The adapter should handle reconnection on relay disconnects
- Error handling — NWC responses include error codes (
QUOTA_EXCEEDED, INSUFFICIENT_BALANCE, NOT_IMPLEMENTED, etc.) that should be mapped to user-friendly messages
- Budget/spending limits — Some NWC implementations support per-connection budgets. This could be surfaced in routstrd's config for additional safety
Open Questions
References
Summary
Add support for NWC (Nostr Wallet Connect) as an alternative wallet backend, allowing users to connect any Lightning wallet via the NIP-47 protocol instead of requiring
cocod+ Cashu/ecash.Motivation
Currently, routstrd exclusively relies on
cocodas its wallet backend for Cashu token management and Lightning operations. This means users must use Cashu/ecash to fund and operate routstrd. Adding NWC support would:nostr-tools,applesauce-core, andapplesauce-relay, so the Nostr stack is already in placeBackground: What is NWC?
NWC (NIP-47) is a Final-status Nostr Improvement Proposal that defines an E2E-encrypted protocol for apps to interact with a remote Lightning wallet through Nostr relays:
13194— Wallet service info event (advertises supported commands)23194— Client request23195— Wallet service response23197— Wallet notificationnostr+wallet://URI containing relay URL, wallet pubkey, and client secretKey NWC commands relevant to routstrd:
pay_invoicesendBolt11()make_invoicereceiveBolt11()get_balancegetBalances()get_infogetStatus()Proposed Implementation
1. NWC Wallet Client (
src/daemon/wallet/nwc-client.ts)Create an
NwcClientthat implements a compatible interface with the existingCocodClient:Implementation would use
nostr-tools(already a dependency) for:nostr+wallet://)2. NWC Wallet Adapter (
src/daemon/wallet/nwc-adapter.ts)Create a wallet adapter (parallel to the existing cocod-based adapter in
src/daemon/wallet/index.ts) that wrapsNwcClientand conforms to the same interface consumed by the daemon's HTTP routes:3. Wallet Backend Selection
Update the daemon initialization to support selecting a wallet backend:
walletBackend: "cocod" | "nwc"andnwcUri: string | nulltoRoutstrdConfigroutstrd wallet connect-nwc <uri>command to configure the NWC connectionroutstrd onboard("Connect via Lightning wallet (NWC)" vs "Use local Cashu wallet (cocod)")cocodremains the default; NWC is opt-in4. CLI Commands
When NWC is the active backend,
routstrd receive <amount>androutstrd send <invoice>would route through NWC instead of cocod.5. Daemon HTTP API Updates
The daemon's existing HTTP routes (
/wallet/send/bolt11,/wallet/receive/bolt11,/balance, etc.) should transparently route to whichever wallet backend is active. Cashu-specific routes (/wallet/send/cashu,/wallet/receive/cashu) would return a501 Not Supportederror when NWC is the active backend.Implementation Notes
nostr-tools,applesauce-core, andapplesauce-relayare already inpackage.jsonQUOTA_EXCEEDED,INSUFFICIENT_BALANCE,NOT_IMPLEMENTED, etc.) that should be mapped to user-friendly messagesOpen Questions
nostr+wallet://URI be stored in the config file, or kept in a separate secrets store?make_invoice(receive via Lightning)? Fall back to display-only balance mode?References
src/daemon/wallet/index.tssrc/daemon/wallet/cocod-client.ts