---
name: nwc
description: Add Nostr Wallet Connect (NWC), WebLN wallet detection, and Lightning zap functionality (NIP-57) to the application. Use when the user wants to connect a Lightning wallet, send zaps, or display zap counts on Nostr events.
---
# Nostr Wallet Connect (NWC) & Lightning Zaps
This skill adds Lightning wallet connectivity and zap (NIP-57) functionality. It is **not included in the project by default**. When the user wants to add wallet/zap features, follow the setup instructions below to install the files, wire up the provider, and add the required dependencies.
## What This Skill Provides
- **NWC** (Nostr Wallet Connect, NIP-47) — connect remote Lightning wallets via `nostr+walletconnect://` URIs
- **WebLN detection** — auto-detect browser extension wallets (Alby, Mutiny, etc.)
- **NIP-57 Zaps** — create zap requests, fetch LNURL invoices, pay them via NWC/WebLN/QR fallback
- **UI components** — `ZapButton`, `ZapDialog`, `WalletModal` ready to drop into the app
## Files Provided by This Skill
All files live under `.agents/skills/nwc/files/` and must be copied into `src/` preserving the directory structure:
| Skill file | Copy to |
|---|---|
| `files/hooks/useNWC.ts` | `src/hooks/useNWC.ts` |
| `files/hooks/useNWCContext.ts` | `src/hooks/useNWCContext.ts` |
| `files/hooks/useWallet.ts` | `src/hooks/useWallet.ts` |
| `files/hooks/useZaps.ts` | `src/hooks/useZaps.ts` |
| `files/contexts/NWCContext.tsx` | `src/contexts/NWCContext.tsx` |
| `files/components/WalletModal.tsx` | `src/components/WalletModal.tsx` |
| `files/components/ZapDialog.tsx` | `src/components/ZapDialog.tsx` |
| `files/components/ZapButton.tsx` | `src/components/ZapButton.tsx` |
## Setup Instructions
### 1. Install Dependencies
Add these packages to the project:
```bash
npm install @getalby/sdk @webbtc/webln-types
```
- `@getalby/sdk` — NWC client used by `useNWC.ts` to open connections and pay invoices
- `@webbtc/webln-types` — TypeScript types for the browser WebLN provider
`ZapDialog` also uses the `qrcode` package to render invoices. It should already be installed in the project (it's used by `src/components/ui/qrcode.tsx`). If it's missing for any reason, also run `npm install qrcode && npm install --save-dev @types/qrcode`.
### 2. Copy the Skill Files Into `src/`
Copy every file listed in the table above from `.agents/skills/nwc/files/` into its corresponding location under `src/`. Preserve the exact paths — the files use `@/hooks/...` / `@/contexts/...` / `@/components/...` imports that depend on these locations.
### 3. Wire Up the `NWCProvider` in `src/App.tsx`
The `NWCProvider` must wrap any component that uses NWC, wallet, or zap hooks/components. Place it inside `NostrProvider` (it depends on a logged-in user) and around `TooltipProvider`:
```tsx
// Add this import near the other provider imports at the top of src/App.tsx
import { NWCProvider } from '@/contexts/NWCContext';
// Then wrap TooltipProvider with NWCProvider inside the existing provider tree:
export function App() {
return (
);
}
```
### 4. Wire Up `NWCProvider` in `src/test/TestApp.tsx` (if tests need it)
If you're writing tests that mount components depending on NWC/zap/wallet hooks, wrap `TestApp`'s provider tree the same way:
```tsx
import { NWCProvider } from '@/contexts/NWCContext';
// ...
{children}
```
## Hooks
Once installed and wired up, the following hooks are available:
| Hook | Purpose |
|---|---|
| `useNWC` (from `@/hooks/useNWCContext`) | Manage NWC connections — `addConnection`, `removeConnection`, `setActiveConnection`, `getActiveConnection`, `sendPayment` |
| `useWallet` | Unified wallet status — returns `{ hasNWC, webln, activeNWC, preferredMethod }` |
| `useZaps` | Zap functionality — fetch zap receipts/totals, create invoices, pay via NWC/WebLN, fallback to QR |
### `useNWC`
```tsx
import { useNWC } from '@/hooks/useNWCContext';
function MyComponent() {
const {
connections, // NWCConnection[]
activeConnection, // string | null (connection string of active wallet)
addConnection, // (uri: string, alias?: string) => Promise
removeConnection, // (connectionString: string) => void
setActiveConnection,// (connectionString: string) => void
getActiveConnection,// () => NWCConnection | null
sendPayment, // (connection, invoice) => Promise<{ preimage: string }>
} = useNWC();
}
```
Connections are persisted to `localStorage` under the keys `nwc-connections` and `nwc-active-connection`.
### `useWallet`
```tsx
import { useWallet } from '@/hooks/useWallet';
function ZapContext() {
const { hasNWC, webln, activeNWC, preferredMethod } = useWallet();
// preferredMethod: 'nwc' | 'webln' | 'manual'
}
```
`useWallet` combines NWC connection state with the browser's global `window.webln` to give a single source of truth for "can this user pay a Lightning invoice right now, and how?"
### `useZaps`
```tsx
import { useZaps } from '@/hooks/useZaps';
import { useWallet } from '@/hooks/useWallet';
import type { Event } from 'nostr-tools';
function MyZapTotal({ event }: { event: Event }) {
const { webln, activeNWC } = useWallet();
const { zapCount, totalSats, zap, isZapping, invoice } = useZaps(event, webln, activeNWC);
return
{totalSats} sats from {zapCount} zaps
;
}
```
The `zap(amount, comment)` function will:
1. Look up the author's LNURL endpoint from their profile's `lud16`/`lud06`
2. Create and sign a NIP-57 zap request
3. Fetch a Lightning invoice from the LNURL service
4. Try to pay it via NWC (preferred), then WebLN, then expose the invoice for QR/manual payment
5. Show toast feedback and invalidate zap query caches on success
## Components
### `ZapButton`
Drop-in button that shows total sats for an event and opens a zap dialog when clicked. Hides itself automatically if the viewer isn't logged in, is the author, or the author has no Lightning address.
```tsx
import { ZapButton } from '@/components/ZapButton';
```
Pre-computed zap data can be passed in to avoid extra queries in feed views:
```tsx
```
### `ZapDialog`
The full zap flow UI (amount presets, custom amount, comment, invoice QR). Usually rendered indirectly via `ZapButton`, but can be used standalone for custom triggers:
```tsx
import { ZapDialog } from '@/components/ZapDialog';
```
### `WalletModal`
Settings UI for connecting/removing NWC wallets and viewing WebLN status. Good for a wallet settings page or a header icon:
```tsx
import { WalletModal } from '@/components/WalletModal';
// Default trigger button
// Or custom trigger
```
## Payment Method Priority
When a user sends a zap, the skill tries payment methods in this order:
1. **NWC** — if there is an active connection in `useNWC`, `sendPayment` is called with the fetched invoice
2. **WebLN** — if `window.webln` exists, it's `enable()`d (if needed) and used to pay the invoice
3. **Manual/QR** — the invoice is exposed as a QR code and `lightning:` URI; the user pays from any wallet
Errors at each stage fall through to the next method, with a toast explaining what happened.
## Protocol Notes
- **NIP-47 (NWC)** — wallet connection strings start with `nostr+walletconnect://` (or legacy `nostrwalletconnect://`). The `@getalby/sdk` `LN` client handles the underlying encrypted request/response events with the wallet service pubkey over relays defined in the URI.
- **NIP-57 (Zaps)** — `nip57.makeZapRequest` builds the kind-9734 zap request. For addressable events (kinds 30000–39999) the skill passes the event object (so an `a` tag is included); for all other kinds it passes just the event id (so an `e` tag is included). The zap request is **signed but not published** — it's sent directly to the LNURL endpoint in the query string, which returns a BOLT11 invoice.
- **LNURL** — resolved from the author's `lud16` (email-style) or `lud06` (bech32) metadata field via `nip57.getZapEndpoint`.
- **Zap receipts** — kind 9735 events published by the LNURL service after payment. The skill queries them by `#e` (regular events) or `#a` (addressable events) and sums sats from the `amount` tag, BOLT11 invoice, or zap request description (in that order of preference).
## Tips
- **Loading the skill doesn't modify the project.** You must actually copy the files and edit `App.tsx` and `package.json` as described above. Without the provider, every hook will throw `"useNWC must be used within a NWCProvider"`.
- **Place `ZapButton` next to other engagement buttons** (reply, repost, like) on posts. It returns `null` for self-authored posts and authors without a Lightning address, so it's safe to render unconditionally.
- **Don't store connection strings anywhere else.** NWC connection strings contain a secret key. The skill keeps them in `localStorage` only; treat them like passwords.
- **The `WebLNProvider` type import is `import type { WebLNProvider } from '@webbtc/webln-types';`** — types-only, so it disappears at build time.