A multi-tenant x402 facilitator for Lightning Network payments, built on Nostr Wallet Connect (NWC).
┌─────────────────────────────────────────────────────────┐
│ Facilitator endpoints │
│ POST /register register a merchant NWC secret │
│ POST /invoice generate BOLT11 for a merchant │
│ POST /verify check preimage cryptographically │
│ POST /settle confirm payment via merchant NWC │
│ GET /supported capability discovery │
│ GET /invoice/status/:hash poll payment status │
├─────────────────────────────────────────────────────────┤
│ Demo (enabled when DEMO_NWC_SECRET is set) │
│ GET /demo/quote pay ~$0.01 → get a Satoshi quote │
└─────────────────────────────────────────────────────────┘
The facilitator holds no wallet credentials in its config. Each merchant calls POST /register with their NWC connection string and receives an opaque merchantId (UUID). The NWC secret is stored in Redis; clients only ever see the UUID. The facilitator uses it to:
- Create a BOLT11 invoice against the merchant's wallet (
POST /invoice) - Confirm settlement against the merchant's wallet (
POST /settle)
Multiple merchants can share one facilitator instance, each with their own independent wallet.
Unlike EVM-based x402 where the client signs a transaction, Lightning requires a BOLT11 invoice to be generated server-side before the client pays:
- Client
GET /resource→ server returns402with invoice inextra.invoice - Client pays the BOLT11 invoice → receives a
preimage - Client retries with
preimagein thepayment-signatureheader - Server calls
/verify(checkssha256(preimage) == paymentHash, no network call) - Server calls
/settle(calls NWClookup_invoiceto confirmsettled_at)
- Node.js 18+
- Redis (local or hosted — e.g. Upstash)
- At least one NWC-compatible wallet (e.g. Alby):
- Merchant wallet — receives payments
- Sender wallet — sends payments during e2e tests (optional)
npm installCopy and fill in .env:
# Required
REDIS_URL=redis://localhost:6379
# Optional — port defaults to 3000
PORT=3000
# Optional — public base URL (used in /.well-known/x402 response)
BASE_URL=https://your-domain.com
# Optional — enables the /demo/quote endpoint
DEMO_NWC_SECRET=nostr+walletconnect://<pubkey>?relay=<relay>&secret=<secret>For e2e tests, copy .env.sender.example to .env.sender and fill in a sender wallet NWC URL.
npm run devcurl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{"nwcSecret":"nostr+walletconnect://..."}'
# → { "merchantId": "<uuid>" }Use the merchantId in your resource server's extra.merchantId field.
Current behaviour: verify checks the preimage cryptographically (no network call). settle calls the merchant's NWC wallet to confirm the invoice is paid.
The problem: In Lightning, payment is atomic — once the preimage is revealed, the payment is already settled. There is no separate "settle" step at the protocol level.
Hold invoices as a solution: HODL invoices would let the facilitator hold funds in-flight during verify and release them at settle. Requires NWC wallet support (not universally available yet).
NWC clients are cached indefinitely by connection string. There is no reconnection logic or health-check. Long-lived connections may silently drop. Consider adding a ping/reconnect strategy.