Skip to content

Commit bdd4f6b

Browse files
authored
feat: add NWC payments processor (#323) (#539)
1 parent f5ba023 commit bdd4f6b

15 files changed

Lines changed: 1073 additions & 6 deletions

File tree

.changeset/seven-lines-heal.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nostream": minor
3+
---
4+
5+
Add NWC (NIP-47) as a payments processor for admission invoices, including configurable invoice expiry and reply timeout handling, compatibility for legacy NWC URI schemes, and docs/env updates.

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ WORKER_COUNT=2 # Defaults to CPU count. Use 1 or 2 for local testing.
4141
# NODELESS_WEBHOOK_SECRET=
4242
# OPENNODE_API_KEY=
4343
# LNBITS_API_KEY=
44+
# ALBY_NWC_URL=nostr+walletconnect://<wallet-service-pubkey>?relay=<wss-relay-url>&secret=<secret>
4445

4546
# --- READ REPLICAS (Optional) ---
4647
# READ_REPLICA_ENABLED=false

CONFIGURATION.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ The following environment variables can be set:
5757
| NOSTR_CONFIG_DIR | Configuration directory | <project_root>/.nostr/ |
5858
| DEBUG | Debugging filter | |
5959
| ZEBEDEE_API_KEY | Zebedee Project API Key | |
60+
| NWC_URL | NWC connection URL (`nostr+walletconnect://...`) | |
6061

6162
## I2P
6263

@@ -222,5 +223,5 @@ The settings below are listed in alphabetical order by name. Please keep this ta
222223
| payments.feeSchedules.admission[].enabled | Enables admission fee. Defaults to false. |
223224
| payments.feeSchedules.admission[].whitelists.event_kinds | List of event kinds to waive admission fee. Use `[min, max]` for ranges. |
224225
| payments.feeSchedules.admission[].whitelists.pubkeys | List of pubkeys to waive admission fee. |
225-
| payments.processor | Either `zebedee`, `lnbits`, `lnurl`. |
226+
| payments.processor | Either `zebedee`, `lnbits`, `lnurl`, `nodeless`, `opennode`, `nwc`. |
226227
| workers.count | Number of workers to spin up to handle incoming connections. Spin workers as many CPUs are available when set to zero. Defaults to zero. |

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Install Docker from their [official guide](https://docs.docker.com/engine/instal
9797
- Set `payments.enabled` to `true`
9898
- Set `payments.feeSchedules.admission.enabled` to `true`
9999
- Set `limits.event.pubkey.minBalance` to the minimum balance in msats required to accept events (i.e. `1000000` to require a balance of `1000` sats)
100-
- Choose one of the following payment processors: `zebedee`, `nodeless`, `opennode`, `lnbits`, `lnurl`
100+
- Choose one of the following payment processors: `zebedee`, `nodeless`, `opennode`, `lnbits`, `lnurl`, `nwc`
101101

102102
2. [ZEBEDEE](https://zebedee.io)
103103
- Complete the step "Before you begin"
@@ -172,7 +172,20 @@ Install Docker from their [official guide](https://docs.docker.com/engine/instal
172172
- Set `lnurl.invoiceURL` to your LNURL (e.g. `https://getalby.com/lnurlp/your-username`)
173173
- Restart Nostream (`nostream stop` followed by `nostream start`)
174174
175-
7. Ensure payments are required for your public key
175+
7. Nostr Wallet Connect (NIP-47 / NWC)
176+
- Complete the step "Before you begin"
177+
- Create an app connection in your NWC-compatible wallet and copy the generated NWC URL
178+
- Set `NWC_URL` environment variable on your `.env` file
179+
180+
```
181+
NWC_URL={NOSTR_WALLET_CONNECT_URL}
182+
```
183+
184+
- On your `.nostr/settings.yaml` file make the following changes:
185+
- Set `payments.processor` to `nwc`
186+
- Restart Nostream (`./scripts/stop` followed by `./scripts/start`)
187+
188+
8. Ensure payments are required for your public key
176189
- Visit https://{YOUR-DOMAIN}/
177190
- You should be presented with a form requesting an admission fee to be paid
178191
- Fill out the form and take the necessary steps to pay the invoice

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"node": ">=24.14.1"
153153
},
154154
"dependencies": {
155+
"@getalby/sdk": "^5.0.0",
155156
"@clack/prompts": "^1.2.0",
156157
"@noble/secp256k1": "1.7.1",
157158
"accepts": "^1.3.8",

pnpm-lock.yaml

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/default-settings.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ paymentsProcessors:
3939
opennode:
4040
baseURL: api.opennode.com
4141
callbackBaseURL: https://nostream.your-domain.com/callbacks/opennode
42+
nwc:
43+
invoiceExpirySeconds: 900
44+
replyTimeoutMs: 10000
4245
nip05:
4346
# NIP-05 verification of event authors as a spam reduction measure.
4447
# mode: 'enabled' requires NIP-05 for publishing (except kind 0),

src/@types/settings.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ export interface OpenNodePaymentsProcessor {
203203
callbackBaseURL: string
204204
}
205205

206-
export interface NodelessPaymentsProcessor {
207-
baseURL: string
208-
storeId: string
206+
export interface NwcPaymentsProcessor {
207+
invoiceExpirySeconds: number
208+
replyTimeoutMs: number
209209
}
210210

211211
export interface PaymentsProcessors {
@@ -214,6 +214,7 @@ export interface PaymentsProcessors {
214214
lnbits?: LNbitsPaymentsProcessor
215215
nodeless?: NodelessPaymentsProcessor
216216
opennode?: OpenNodePaymentsProcessor
217+
nwc?: NwcPaymentsProcessor
217218
}
218219

219220
export interface Local {

src/factories/payments-processor-factory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createLNbitsPaymentProcessor } from './payments-processors/lnbits-payme
22
import { createLnurlPaymentsProcessor } from './payments-processors/lnurl-payments-processor-factory'
33
import { createLogger } from './logger-factory'
44
import { createNodelessPaymentsProcessor } from './payments-processors/nodeless-payments-processor-factory'
5+
import { createNwcPaymentsProcessor } from './payments-processors/nwc-payments-processor-factory'
56
import { createOpenNodePaymentsProcessor } from './payments-processors/opennode-payments-processor-factory'
67
import { createSettings } from './settings-factory'
78
import { createZebedeePaymentsProcessor } from './payments-processors/zebedee-payments-processor-factory'
@@ -29,6 +30,8 @@ export const createPaymentsProcessor = (): IPaymentsProcessor => {
2930
return createNodelessPaymentsProcessor(settings)
3031
case 'opennode':
3132
return createOpenNodePaymentsProcessor(settings)
33+
case 'nwc':
34+
return createNwcPaymentsProcessor(settings)
3235
default:
3336
return new NullPaymentsProcessor()
3437
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { createSettings } from '../settings-factory'
2+
import { NwcPaymentsProcessor } from '../../payments-processors/nwc-payments-processor'
3+
import { createLogger } from '../logger-factory'
4+
import { IPaymentsProcessor } from '../../@types/clients'
5+
import { Settings } from '../../@types/settings'
6+
7+
const logger = createLogger('nwc-payments-processor-factory')
8+
9+
const getNwcConfig = (settings: Settings): { nwcUrl: string; replyTimeoutMs: number } => {
10+
const nwcUrl = process.env.NWC_URL
11+
12+
if (!nwcUrl) {
13+
const error = new Error('NWC_URL must be set.')
14+
logger('Unable to create NWC payments processor. %o', error)
15+
throw error
16+
}
17+
18+
if (!nwcUrl.startsWith('nostr+walletconnect://') && !nwcUrl.startsWith('nostrwalletconnect://')) {
19+
const error = new Error('NWC_URL must be a valid nostr+walletconnect:// or nostrwalletconnect:// URI.')
20+
logger('Unable to create NWC payments processor. %o', error)
21+
throw error
22+
}
23+
24+
try {
25+
new URL(nwcUrl)
26+
} catch {
27+
const error = new Error('NWC_URL is not parseable as a URL.')
28+
logger('Unable to create NWC payments processor. %o', error)
29+
throw error
30+
}
31+
32+
const replyTimeoutMs = settings.paymentsProcessors?.nwc?.replyTimeoutMs
33+
if (typeof replyTimeoutMs !== 'number' || replyTimeoutMs <= 0) {
34+
const error = new Error('Setting paymentsProcessors.nwc.replyTimeoutMs must be a positive number.')
35+
logger('Unable to create NWC payments processor. %o', error)
36+
throw error
37+
}
38+
39+
const invoiceExpirySeconds = settings.paymentsProcessors?.nwc?.invoiceExpirySeconds
40+
if (typeof invoiceExpirySeconds !== 'number' || !Number.isInteger(invoiceExpirySeconds) || invoiceExpirySeconds <= 0) {
41+
const error = new Error('Setting paymentsProcessors.nwc.invoiceExpirySeconds must be a positive integer.')
42+
logger('Unable to create NWC payments processor. %o', error)
43+
throw error
44+
}
45+
46+
return { nwcUrl, replyTimeoutMs }
47+
}
48+
49+
export const createNwcPaymentsProcessor = (settings: Settings): IPaymentsProcessor => {
50+
const { nwcUrl, replyTimeoutMs } = getNwcConfig(settings)
51+
52+
return new NwcPaymentsProcessor(nwcUrl, replyTimeoutMs, createSettings)
53+
}

0 commit comments

Comments
 (0)