nostr-bray 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli/commands/wallet.d.ts +2 -2
- package/dist/cli/commands/wallet.js +13 -11
- package/dist/cli/commands/zap.js +4 -2
- package/dist/cli/index.js +3 -3
- package/dist/config.js +46 -31
- package/dist/help.js +4 -4
- package/dist/marketplace/tools.js +9 -6
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +1 -1
- package/dist/zap/handlers.d.ts +48 -144
- package/dist/zap/handlers.js +177 -223
- package/dist/zap/index.d.ts +1 -0
- package/dist/zap/index.js +1 -0
- package/dist/zap/nwc-file.d.ts +5 -0
- package/dist/zap/nwc-file.js +65 -0
- package/dist/zap/tools.js +20 -15
- package/llms-full.txt +7 -6
- package/llms.txt +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -212,13 +212,17 @@ Search order: `BRAY_CONFIG` env var > `$XDG_CONFIG_HOME/bray/config.json` > `~/.
|
|
|
212
212
|
| `NOSTR_SECRET_KEY_FILE` | Path to secret key file |
|
|
213
213
|
| `NOSTR_NCRYPTSEC` | NIP-49 encrypted key |
|
|
214
214
|
| `NOSTR_NCRYPTSEC_PASSWORD` | Password for ncryptsec |
|
|
215
|
+
| `NWC_URI_FILE` | Path to a private `0600` file containing the NWC bearer URI |
|
|
215
216
|
| `NOSTR_RELAYS` | Comma-separated relay URLs |
|
|
216
217
|
| `TOR_PROXY` | SOCKS5h proxy for Tor |
|
|
217
218
|
| `NIP04_ENABLED` | Set `1` to enable legacy NIP-04 DMs |
|
|
218
219
|
| `TRANSPORT` | `stdio` (default) or `http` |
|
|
219
220
|
| `PORT` | HTTP port (default 3000) |
|
|
220
221
|
|
|
221
|
-
All secret env vars are deleted from `process.env`
|
|
222
|
+
All secret env vars are deleted from `process.env` before parsing can fail.
|
|
223
|
+
Raw `NWC_URI` is refused; use `NWC_URI_FILE` or `wallet connect <nwc-file>` so
|
|
224
|
+
the bearer credential never appears in a process environment, command argument
|
|
225
|
+
or MCP tool argument.
|
|
222
226
|
|
|
223
227
|
## CLI
|
|
224
228
|
|
|
@@ -7,8 +7,8 @@ export interface WalletExtras {
|
|
|
7
7
|
* Dispatch `wallet *` subcommands (NIP-47 Nostr Wallet Connect).
|
|
8
8
|
*
|
|
9
9
|
* Commands:
|
|
10
|
-
* wallet connect <nwc-
|
|
11
|
-
* wallet disconnect Remove the stored NWC
|
|
10
|
+
* wallet connect <nwc-file> Store an NWC secret-file reference for the active identity
|
|
11
|
+
* wallet disconnect Remove the stored NWC file reference for the active identity
|
|
12
12
|
* wallet status Show configured wallet pubkey and relay
|
|
13
13
|
* wallet pay <bolt11> Pay a Lightning invoice via NWC
|
|
14
14
|
* wallet balance Request wallet balance via NWC
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { handleZapSend, handleZapBalance, handleZapListTransactions, resolveNwcUri, loadWallets, saveWallets, parseNwcUri, } from '../../exports.js';
|
|
1
|
+
import { handleZapSend, handleZapBalance, handleZapListTransactions, resolveNwcUri, loadWallets, saveWallets, parseNwcUri, normaliseNwcUriFile, } from '../../exports.js';
|
|
2
2
|
/**
|
|
3
3
|
* Dispatch `wallet *` subcommands (NIP-47 Nostr Wallet Connect).
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* wallet connect <nwc-
|
|
7
|
-
* wallet disconnect Remove the stored NWC
|
|
6
|
+
* wallet connect <nwc-file> Store an NWC secret-file reference for the active identity
|
|
7
|
+
* wallet disconnect Remove the stored NWC file reference for the active identity
|
|
8
8
|
* wallet status Show configured wallet pubkey and relay
|
|
9
9
|
* wallet pay <bolt11> Pay a Lightning invoice via NWC
|
|
10
10
|
* wallet balance Request wallet balance via NWC
|
|
@@ -15,13 +15,11 @@ export async function dispatch(cmd, _cmdArgs, h, ctx, pool, extras) {
|
|
|
15
15
|
const { globalNwcUri, walletsFile } = extras;
|
|
16
16
|
switch (cmd) {
|
|
17
17
|
case 'wallet-connect': {
|
|
18
|
-
const
|
|
19
|
-
// Validate URI before storing
|
|
20
|
-
parseNwcUri(uri);
|
|
18
|
+
const secretFile = normaliseNwcUriFile(req(1, 'wallet connect <nwc-file>'));
|
|
21
19
|
const pubkey = ctx.activePublicKeyHex;
|
|
22
20
|
const npub = ctx.activeNpub;
|
|
23
21
|
const wallets = loadWallets(walletsFile);
|
|
24
|
-
wallets[pubkey] =
|
|
22
|
+
wallets[pubkey] = secretFile;
|
|
25
23
|
saveWallets(walletsFile, wallets);
|
|
26
24
|
out({ ok: true, identity: npub, message: `Wallet configured for ${npub}` });
|
|
27
25
|
break;
|
|
@@ -40,7 +38,7 @@ export async function dispatch(cmd, _cmdArgs, h, ctx, pool, extras) {
|
|
|
40
38
|
const npub = ctx.activeNpub;
|
|
41
39
|
const uri = resolveNwcUri(ctx, walletsFile, globalNwcUri);
|
|
42
40
|
if (!uri) {
|
|
43
|
-
out({ ok: false, identity: npub, configured: false, message: 'No wallet configured. Use `wallet connect <nwc-
|
|
41
|
+
out({ ok: false, identity: npub, configured: false, message: 'No wallet configured. Use `wallet connect <nwc-file>` or set NWC_URI_FILE.' });
|
|
44
42
|
break;
|
|
45
43
|
}
|
|
46
44
|
const conn = parseNwcUri(uri);
|
|
@@ -48,12 +46,16 @@ export async function dispatch(cmd, _cmdArgs, h, ctx, pool, extras) {
|
|
|
48
46
|
out({ ok: true, identity: npub, configured: true, walletPubkey: conn.pubkey, relay: conn.relay });
|
|
49
47
|
break;
|
|
50
48
|
}
|
|
51
|
-
case 'wallet-pay':
|
|
52
|
-
|
|
49
|
+
case 'wallet-pay': {
|
|
50
|
+
const result = await handleZapSend(ctx, pool, {
|
|
53
51
|
invoice: req(1, 'wallet pay <bolt11>'),
|
|
54
52
|
nwcUri: resolveNwcUri(ctx, walletsFile, globalNwcUri),
|
|
55
|
-
})
|
|
53
|
+
});
|
|
54
|
+
// A preimage can be an L402 bearer credential when the challenge macaroon
|
|
55
|
+
// is known. Report verified payment without printing the preimage.
|
|
56
|
+
out({ paid: true, verified: result.verified, paymentHash: result.paymentHash, feesPaidMsats: result.fees_paid });
|
|
56
57
|
break;
|
|
58
|
+
}
|
|
57
59
|
case 'wallet-balance':
|
|
58
60
|
out(await handleZapBalance(ctx, pool, { nwcUri: resolveNwcUri(ctx, walletsFile, globalNwcUri) }));
|
|
59
61
|
break;
|
package/dist/cli/commands/zap.js
CHANGED
|
@@ -4,9 +4,11 @@ export async function dispatch(cmd, cmdArgs, h, ctx, pool, extras) {
|
|
|
4
4
|
const { req, flag, out } = h;
|
|
5
5
|
const { globalNwcUri, walletsFile } = extras;
|
|
6
6
|
switch (cmd) {
|
|
7
|
-
case 'zap-send':
|
|
8
|
-
|
|
7
|
+
case 'zap-send': {
|
|
8
|
+
const result = await handleZapSend(ctx, pool, { invoice: req(1, 'zap-send <bolt11>'), nwcUri: resolveNwcUri(ctx, walletsFile, globalNwcUri) });
|
|
9
|
+
out({ paid: true, verified: result.verified, paymentHash: result.paymentHash, feesPaidMsats: result.fees_paid });
|
|
9
10
|
break;
|
|
11
|
+
}
|
|
10
12
|
case 'zap-balance':
|
|
11
13
|
out(await handleZapBalance(ctx, pool, { nwcUri: resolveNwcUri(ctx, walletsFile, globalNwcUri) }));
|
|
12
14
|
break;
|
package/dist/cli/index.js
CHANGED
|
@@ -182,8 +182,8 @@ Admin (NIP-86 relay management):
|
|
|
182
182
|
listdisallowedkinds; they were never NIP-86 method names)
|
|
183
183
|
|
|
184
184
|
Wallet (NIP-47 Nostr Wallet Connect):
|
|
185
|
-
wallet connect <nwc-
|
|
186
|
-
wallet disconnect Remove stored NWC
|
|
185
|
+
wallet connect <nwc-file> Store a private NWC secret-file reference
|
|
186
|
+
wallet disconnect Remove stored NWC file reference
|
|
187
187
|
wallet status Show configured wallet pubkey and relay
|
|
188
188
|
wallet pay <bolt11> Pay a Lightning invoice via NWC
|
|
189
189
|
wallet balance Request wallet balance via NWC
|
|
@@ -261,7 +261,7 @@ Environment:
|
|
|
261
261
|
BUNKER_URI / BUNKER_URI_FILE bunker:// URI (use INSTEAD of secret key)
|
|
262
262
|
NOSTR_RELAYS Comma-separated relay URLs
|
|
263
263
|
NOSTR_AUTH NIP-42 policy: off (default), on-demand, eager
|
|
264
|
-
|
|
264
|
+
NWC_URI_FILE Private 0600 file containing an NWC URI
|
|
265
265
|
TOR_PROXY SOCKS5h proxy URL
|
|
266
266
|
NOSTR_BRAY_OUTPUT Default output: "human" (default) or "json"
|
|
267
267
|
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
+
import { readNwcUriFile } from './zap/nwc-file.js';
|
|
4
5
|
const NSEC_RE = /^nsec1[a-z0-9]{58}$/;
|
|
5
6
|
const HEX_RE = /^[0-9a-f]{64}$/;
|
|
6
7
|
const NCRYPTSEC_RE = /^ncryptsec1[a-z0-9]+$/;
|
|
@@ -85,10 +86,34 @@ export function loadConfigFile() {
|
|
|
85
86
|
/** Load configuration from config file, environment variables, and secret files.
|
|
86
87
|
* Priority: env vars > config file > defaults */
|
|
87
88
|
export async function loadConfig() {
|
|
89
|
+
// Capture then remove bearer credentials before any parsing or validation can
|
|
90
|
+
// throw. This keeps failure paths from leaving secrets in process.env.
|
|
91
|
+
const secretEnvironment = {
|
|
92
|
+
secretKey: process.env.NOSTR_SECRET_KEY,
|
|
93
|
+
secretKeyFile: process.env.NOSTR_SECRET_KEY_FILE,
|
|
94
|
+
bunkerUri: process.env.BUNKER_URI,
|
|
95
|
+
bunkerUriFile: process.env.BUNKER_URI_FILE,
|
|
96
|
+
ncryptsec: process.env.NOSTR_NCRYPTSEC,
|
|
97
|
+
ncryptsecFile: process.env.NOSTR_NCRYPTSEC_FILE,
|
|
98
|
+
ncryptsecPassword: process.env.NOSTR_NCRYPTSEC_PASSWORD,
|
|
99
|
+
ncryptsecPasswordFile: process.env.NOSTR_NCRYPTSEC_PASSWORD_FILE,
|
|
100
|
+
nwcUri: process.env.NWC_URI,
|
|
101
|
+
nwcUriFile: process.env.NWC_URI_FILE,
|
|
102
|
+
};
|
|
103
|
+
delete process.env.NOSTR_SECRET_KEY;
|
|
104
|
+
delete process.env.NOSTR_SECRET_KEY_FILE;
|
|
105
|
+
delete process.env.NWC_URI;
|
|
106
|
+
delete process.env.NWC_URI_FILE;
|
|
107
|
+
delete process.env.BUNKER_URI;
|
|
108
|
+
delete process.env.BUNKER_URI_FILE;
|
|
109
|
+
delete process.env.NOSTR_NCRYPTSEC;
|
|
110
|
+
delete process.env.NOSTR_NCRYPTSEC_FILE;
|
|
111
|
+
delete process.env.NOSTR_NCRYPTSEC_PASSWORD;
|
|
112
|
+
delete process.env.NOSTR_NCRYPTSEC_PASSWORD_FILE;
|
|
88
113
|
const file = loadConfigFile();
|
|
89
114
|
// --- Secret key ---
|
|
90
|
-
const keyFilePath =
|
|
91
|
-
const keyEnvVar =
|
|
115
|
+
const keyFilePath = secretEnvironment.secretKeyFile ?? file.secretKeyFile;
|
|
116
|
+
const keyEnvVar = secretEnvironment.secretKey;
|
|
92
117
|
let secretKey;
|
|
93
118
|
// A local key source is an explicit choice to sign locally. A bunker URI
|
|
94
119
|
// saved by `bunker connect` is only a fallback and must not override that
|
|
@@ -96,15 +121,15 @@ export async function loadConfig() {
|
|
|
96
121
|
// it is present. Explicit bunker configuration still takes precedence.
|
|
97
122
|
const hasLocalKeySource = !!(keyFilePath
|
|
98
123
|
|| keyEnvVar
|
|
99
|
-
||
|
|
100
|
-
||
|
|
124
|
+
|| secretEnvironment.ncryptsecFile
|
|
125
|
+
|| secretEnvironment.ncryptsec
|
|
101
126
|
|| file.ncryptsecFile);
|
|
102
127
|
let bunkerUri;
|
|
103
|
-
if (
|
|
104
|
-
bunkerUri = readSecretFile(
|
|
128
|
+
if (secretEnvironment.bunkerUriFile) {
|
|
129
|
+
bunkerUri = readSecretFile(secretEnvironment.bunkerUriFile);
|
|
105
130
|
}
|
|
106
|
-
else if (
|
|
107
|
-
bunkerUri =
|
|
131
|
+
else if (secretEnvironment.bunkerUri) {
|
|
132
|
+
bunkerUri = secretEnvironment.bunkerUri;
|
|
108
133
|
}
|
|
109
134
|
else if (file.bunkerUriFile) {
|
|
110
135
|
bunkerUri = readSecretFile(file.bunkerUriFile);
|
|
@@ -118,10 +143,10 @@ export async function loadConfig() {
|
|
|
118
143
|
bunkerUri = saved.uri;
|
|
119
144
|
}
|
|
120
145
|
// --- NIP-49 ncryptsec (password-encrypted key) ---
|
|
121
|
-
const ncryptsec =
|
|
122
|
-
? readSecretFile(
|
|
123
|
-
:
|
|
124
|
-
?
|
|
146
|
+
const ncryptsec = secretEnvironment.ncryptsecFile
|
|
147
|
+
? readSecretFile(secretEnvironment.ncryptsecFile)
|
|
148
|
+
: secretEnvironment.ncryptsec
|
|
149
|
+
? secretEnvironment.ncryptsec
|
|
125
150
|
: file.ncryptsecFile
|
|
126
151
|
? readSecretFile(file.ncryptsecFile)
|
|
127
152
|
: undefined;
|
|
@@ -129,11 +154,11 @@ export async function loadConfig() {
|
|
|
129
154
|
// Prefer file-based sources so the password can be delivered via systemd
|
|
130
155
|
// credentials, docker secrets, or similar secure channels and never appears
|
|
131
156
|
// in the process environment or config JSON.
|
|
132
|
-
const ncryptsecPasswordFile =
|
|
157
|
+
const ncryptsecPasswordFile = secretEnvironment.ncryptsecPasswordFile ?? file.ncryptsecPasswordFile;
|
|
133
158
|
let ncryptsecPasswordBuf = null;
|
|
134
159
|
let ncryptsecPassword;
|
|
135
|
-
if (
|
|
136
|
-
ncryptsecPassword =
|
|
160
|
+
if (secretEnvironment.ncryptsecPassword) {
|
|
161
|
+
ncryptsecPassword = secretEnvironment.ncryptsecPassword;
|
|
137
162
|
}
|
|
138
163
|
else if (ncryptsecPasswordFile) {
|
|
139
164
|
// Read as Buffer so we can zeroise the at-rest byte representation even
|
|
@@ -191,13 +216,14 @@ export async function loadConfig() {
|
|
|
191
216
|
}
|
|
192
217
|
const secretFormat = secretKey ? detectKeyFormat(secretKey) : 'nsec';
|
|
193
218
|
// --- NWC URI ---
|
|
194
|
-
const
|
|
219
|
+
const rawNwcUri = secretEnvironment.nwcUri;
|
|
220
|
+
const nwcFilePath = secretEnvironment.nwcUriFile ?? file.nwcUriFile;
|
|
221
|
+
if (rawNwcUri) {
|
|
222
|
+
throw new Error('NWC_URI is disabled because bearer wallet credentials must not be stored in environment variables; use NWC_URI_FILE');
|
|
223
|
+
}
|
|
195
224
|
let nwcUri;
|
|
196
225
|
if (nwcFilePath) {
|
|
197
|
-
nwcUri =
|
|
198
|
-
}
|
|
199
|
-
else if (process.env.NWC_URI) {
|
|
200
|
-
nwcUri = process.env.NWC_URI;
|
|
226
|
+
nwcUri = readNwcUriFile(nwcFilePath);
|
|
201
227
|
}
|
|
202
228
|
// --- Relays ---
|
|
203
229
|
const relays = process.env.NOSTR_RELAYS
|
|
@@ -261,17 +287,6 @@ export async function loadConfig() {
|
|
|
261
287
|
const walletsFile = process.env.BRAY_WALLETS_FILE
|
|
262
288
|
?? file.walletsFile
|
|
263
289
|
?? (process.env.HOME ? `${process.env.HOME}/.nostr/bray-wallets.json` : '');
|
|
264
|
-
// --- Clean up secrets from process.env ---
|
|
265
|
-
delete process.env.NOSTR_SECRET_KEY;
|
|
266
|
-
delete process.env.NOSTR_SECRET_KEY_FILE;
|
|
267
|
-
delete process.env.NWC_URI;
|
|
268
|
-
delete process.env.NWC_URI_FILE;
|
|
269
|
-
delete process.env.BUNKER_URI;
|
|
270
|
-
delete process.env.BUNKER_URI_FILE;
|
|
271
|
-
delete process.env.NOSTR_NCRYPTSEC;
|
|
272
|
-
delete process.env.NOSTR_NCRYPTSEC_FILE;
|
|
273
|
-
delete process.env.NOSTR_NCRYPTSEC_PASSWORD;
|
|
274
|
-
delete process.env.NOSTR_NCRYPTSEC_PASSWORD_FILE;
|
|
275
290
|
return {
|
|
276
291
|
secretKey,
|
|
277
292
|
secretFormat,
|
package/dist/help.js
CHANGED
|
@@ -57,14 +57,14 @@ export const COMMAND_HELP = {
|
|
|
57
57
|
'relay-add': { usage: 'relay-add <url> [read|write]', description: 'Add a relay to the active identity\'s relay set (in-memory only — does not publish kind 10002).', examples: ['nostr-bray relay-add wss://new-relay.com', 'nostr-bray relay-add wss://read-only.com read'] },
|
|
58
58
|
'relay-info': { usage: 'relay-info <wss://url>', description: 'Fetch the NIP-11 relay information document (name, description, supported NIPs, limits).', examples: ['nostr-bray relay-info wss://relay.damus.io'] },
|
|
59
59
|
// Wallet (NIP-47 Nostr Wallet Connect)
|
|
60
|
-
'wallet-connect': { usage: 'wallet connect <nwc-
|
|
61
|
-
'wallet-disconnect': { usage: 'wallet disconnect', description: 'Remove the stored NWC
|
|
60
|
+
'wallet-connect': { usage: 'wallet connect <nwc-file>', description: 'Store a reference to a private local file containing the NWC URI for the active identity.', examples: ['nostr-bray wallet connect /run/secrets/alice-nwc'], notes: 'The file must be a regular 0600 file. The bearer URI is never passed on the command line or stored in the wallet registry.' },
|
|
61
|
+
'wallet-disconnect': { usage: 'wallet disconnect', description: 'Remove the stored NWC file reference for the active identity. Falls back to NWC_URI_FILE if configured.', examples: ['nostr-bray wallet disconnect'] },
|
|
62
62
|
'wallet-status': { usage: 'wallet status', description: 'Show the wallet pubkey and relay configured for the active identity. The connection secret is never shown.', examples: ['nostr-bray wallet status --json'] },
|
|
63
|
-
'wallet-pay': { usage: 'wallet pay <bolt11>', description: 'Pay a Lightning invoice via Nostr Wallet Connect (NIP-47). Requires a wallet configured via `wallet connect` or
|
|
63
|
+
'wallet-pay': { usage: 'wallet pay <bolt11>', description: 'Pay a Lightning invoice via Nostr Wallet Connect (NIP-47). Requires a wallet configured via `wallet connect` or NWC_URI_FILE.', examples: ['nostr-bray wallet pay lnbc10u1...'], notes: 'SPENDS REAL SATS. Waits for an authenticated wallet result and verifies the returned preimage without printing it.' },
|
|
64
64
|
'wallet-balance': { usage: 'wallet balance', description: 'Request wallet balance via Nostr Wallet Connect (NIP-47).', examples: ['nostr-bray wallet balance', 'nostr-bray wallet balance --json'] },
|
|
65
65
|
'wallet-history': { usage: 'wallet history [--limit N]', description: 'List recent Lightning transactions via Nostr Wallet Connect (NIP-47). Default limit: 10.', examples: ['nostr-bray wallet history', 'nostr-bray wallet history --limit 25 --json'] },
|
|
66
66
|
// Zap
|
|
67
|
-
'zap-send': { usage: 'zap-send <bolt11>', description: 'Pay a Lightning invoice via Nostr Wallet Connect (NIP-47). Requires
|
|
67
|
+
'zap-send': { usage: 'zap-send <bolt11>', description: 'Pay a Lightning invoice via Nostr Wallet Connect (NIP-47). Requires NWC_URI_FILE or a persona wallet file.', examples: ['nostr-bray zap-send lnbc10u1...'] },
|
|
68
68
|
'zap-balance': { usage: 'zap-balance', description: 'Request wallet balance via NWC.', examples: ['nostr-bray zap-balance'] },
|
|
69
69
|
'zap-invoice': { usage: 'zap-invoice <msats> [description]', description: 'Generate a Lightning invoice via NWC to receive payments.', examples: ['nostr-bray zap-invoice 100000 "Coffee payment"'] },
|
|
70
70
|
'zap-lookup': { usage: 'zap-lookup <payment-hash>', description: 'Look up a Lightning invoice payment status via NWC.', examples: ['nostr-bray zap-lookup abc123...'] },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { hexId, relayUrl } from '../validation.js';
|
|
3
4
|
import { resolveRecipient } from '../resolve.js';
|
|
4
5
|
import { handleMarketplaceDiscover, handleMarketplaceInspect, handleMarketplaceSearch, handleMarketplaceReputation, handleMarketplaceCompare, handleMarketplaceProbe, handleMarketplaceCall, handleMarketplaceAnnounce, handleMarketplaceUpdate, handleMarketplaceRetire, storeCredential, clearCredentials, extractBolt11AmountSats, } from './handlers.js';
|
|
@@ -146,6 +147,10 @@ export function registerMarketplaceTools(server, deps) {
|
|
|
146
147
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
147
148
|
}, async ({ macaroon, invoice, confirm }) => {
|
|
148
149
|
const decoded = handleZapDecode(invoice);
|
|
150
|
+
if (decoded.expiry === undefined)
|
|
151
|
+
throw new Error('Invalid BOLT-11 invoice');
|
|
152
|
+
if (decoded.amountMsats === undefined)
|
|
153
|
+
throw new Error('Amountless L402 invoices are not supported');
|
|
149
154
|
const costSats = extractBolt11AmountSats(invoice);
|
|
150
155
|
if (!confirm) {
|
|
151
156
|
return {
|
|
@@ -166,17 +171,15 @@ export function registerMarketplaceTools(server, deps) {
|
|
|
166
171
|
invoice,
|
|
167
172
|
nwcUri: resolveNwcUri(deps.ctx, deps.walletsFile, deps.nwcUri),
|
|
168
173
|
});
|
|
169
|
-
//
|
|
170
|
-
const credentialId =
|
|
171
|
-
|
|
172
|
-
// NWC pay_invoice returns asynchronously — use the payment hash as placeholder
|
|
173
|
-
// until the wallet confirms. For now we store with the event id as preimage proxy.
|
|
174
|
-
storeCredential(credentialId, macaroon, credentialId);
|
|
174
|
+
// Keep the bearer credential in process and return only an opaque handle.
|
|
175
|
+
const credentialId = randomUUID();
|
|
176
|
+
storeCredential(credentialId, macaroon, payResult.preimage);
|
|
175
177
|
return {
|
|
176
178
|
content: [{
|
|
177
179
|
type: 'text',
|
|
178
180
|
text: JSON.stringify({
|
|
179
181
|
paid: true,
|
|
182
|
+
verified: payResult.verified,
|
|
180
183
|
credentialId,
|
|
181
184
|
costSats,
|
|
182
185
|
amountMsats: decoded.amountMsats,
|
package/dist/sdk.d.ts
CHANGED
|
@@ -363,6 +363,6 @@ export interface BrayClient {
|
|
|
363
363
|
export declare function createBray(config: BrayClientConfig): Promise<BrayClient>;
|
|
364
364
|
/**
|
|
365
365
|
* Get or create a singleton BrayClient built from environment variables.
|
|
366
|
-
* Reads NOSTR_SECRET_KEY / BUNKER_URI / NOSTR_RELAYS /
|
|
366
|
+
* Reads NOSTR_SECRET_KEY / BUNKER_URI / NOSTR_RELAYS / NWC_URI_FILE etc.
|
|
367
367
|
*/
|
|
368
368
|
export declare function defaultBray(): Promise<BrayClient>;
|
package/dist/sdk.js
CHANGED
|
@@ -343,7 +343,7 @@ export async function createBray(config) {
|
|
|
343
343
|
let _default = null;
|
|
344
344
|
/**
|
|
345
345
|
* Get or create a singleton BrayClient built from environment variables.
|
|
346
|
-
* Reads NOSTR_SECRET_KEY / BUNKER_URI / NOSTR_RELAYS /
|
|
346
|
+
* Reads NOSTR_SECRET_KEY / BUNKER_URI / NOSTR_RELAYS / NWC_URI_FILE etc.
|
|
347
347
|
*/
|
|
348
348
|
export function defaultBray() {
|
|
349
349
|
if (!_default) {
|
package/dist/zap/handlers.d.ts
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GetBalanceResult, NwcRequestOptions, NwcTransaction, NwcTransport, PayInvoiceResult } from '@forgesworn/nwc-kit';
|
|
2
2
|
import type { SigningContext } from '../signing-context.js';
|
|
3
3
|
import type { RelayPool } from '../relay-pool.js';
|
|
4
|
-
import type { PublishResult } from '../types.js';
|
|
5
4
|
/**
|
|
6
5
|
* Load the wallets map from the JSON file. Returns empty map if file missing.
|
|
7
6
|
*
|
|
8
7
|
* @param walletsFile - Absolute path to the JSON wallets store.
|
|
9
|
-
* @returns A map of public key hex strings to NWC
|
|
8
|
+
* @returns A map of public key hex strings to absolute NWC secret-file paths.
|
|
10
9
|
*
|
|
11
10
|
* @example
|
|
12
11
|
* const wallets = loadWallets('/home/user/.bray/wallets.json')
|
|
13
|
-
* // { 'abc123...': '
|
|
12
|
+
* // { 'abc123...': '/run/secrets/alice-nwc' }
|
|
14
13
|
*/
|
|
15
14
|
export declare function loadWallets(walletsFile: string): Record<string, string>;
|
|
16
15
|
/**
|
|
17
16
|
* Save the wallets map to the JSON file. Creates parent dirs and sets 0600.
|
|
18
17
|
*
|
|
19
18
|
* @param walletsFile - Absolute path to the JSON wallets store.
|
|
20
|
-
* @param wallets - Map of public key hex strings to NWC
|
|
19
|
+
* @param wallets - Map of public key hex strings to absolute NWC secret-file paths.
|
|
21
20
|
* @returns void
|
|
22
21
|
*
|
|
23
22
|
* @example
|
|
24
23
|
* saveWallets('/home/user/.bray/wallets.json', {
|
|
25
|
-
* 'abc123...': '
|
|
24
|
+
* 'abc123...': '/run/secrets/alice-nwc',
|
|
26
25
|
* })
|
|
27
26
|
*/
|
|
28
27
|
export declare function saveWallets(walletsFile: string, wallets: Record<string, string>): void;
|
|
29
28
|
/**
|
|
30
29
|
* Resolve the NWC URI for the active identity.
|
|
31
|
-
* 1. Per-identity
|
|
30
|
+
* 1. Per-identity NWC secret file referenced by the wallet registry
|
|
32
31
|
* 2. Global NWC URI (fallback)
|
|
33
32
|
* 3. undefined (no wallet configured)
|
|
34
33
|
*
|
|
@@ -37,27 +36,28 @@ export declare function saveWallets(walletsFile: string, wallets: Record<string,
|
|
|
37
36
|
* @returns The resolved NWC URI, or `undefined` if no wallet is configured.
|
|
38
37
|
*
|
|
39
38
|
* @example
|
|
40
|
-
* const uri = resolveNwcUri(ctx, '/home/user/.bray/wallets.json',
|
|
39
|
+
* const uri = resolveNwcUri(ctx, '/home/user/.bray/wallets.json', configuredNwcUri)
|
|
41
40
|
* if (!uri) throw new Error('No wallet configured')
|
|
42
41
|
*/
|
|
43
42
|
export declare function resolveNwcUri(ctx: SigningContext, walletsFile: string, globalNwcUri?: string): string | undefined;
|
|
44
43
|
export interface NwcConnection {
|
|
45
44
|
pubkey: string;
|
|
46
45
|
relay: string;
|
|
47
|
-
|
|
46
|
+
relays: readonly string[];
|
|
47
|
+
lud16?: string;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Parse a nostr+walletconnect:// URI.
|
|
51
51
|
*
|
|
52
52
|
* @param uri - A `nostr+walletconnect://` URI as specified in NIP-47.
|
|
53
|
-
* @returns
|
|
54
|
-
* @throws {Error} If
|
|
53
|
+
* @returns Public connection metadata. The connection secret is never returned.
|
|
54
|
+
* @throws {Error} If the URI is malformed or insecure.
|
|
55
55
|
*
|
|
56
56
|
* @example
|
|
57
57
|
* const conn = parseNwcUri(
|
|
58
58
|
* 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
59
59
|
* )
|
|
60
|
-
* // { pubkey: 'abc123', relay: 'wss://relay.example',
|
|
60
|
+
* // { pubkey: 'abc123', relay: 'wss://relay.example/', relays: ['wss://relay.example/'] }
|
|
61
61
|
*/
|
|
62
62
|
export declare function parseNwcUri(uri: string): NwcConnection;
|
|
63
63
|
export interface ZapReceipt {
|
|
@@ -87,9 +87,8 @@ export declare function handleZapReceipts(ctx: SigningContext, pool: RelayPool,
|
|
|
87
87
|
* Decode basic bolt11 invoice fields.
|
|
88
88
|
*
|
|
89
89
|
* @param bolt11 - A BOLT-11 Lightning invoice string (e.g. `lnbc...`).
|
|
90
|
-
* @returns
|
|
91
|
-
*
|
|
92
|
-
* yet decoded and will be absent in the current implementation.
|
|
90
|
+
* @returns The bounded amount, description and expiry fields committed by the
|
|
91
|
+
* invoice. `amountMsats` is absent for an amountless invoice.
|
|
93
92
|
*
|
|
94
93
|
* @example
|
|
95
94
|
* const { amountMsats } = handleZapDecode('lnbc1000n1...')
|
|
@@ -100,142 +99,47 @@ export declare function handleZapDecode(bolt11: string): {
|
|
|
100
99
|
description?: string;
|
|
101
100
|
expiry?: number;
|
|
102
101
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
* @param args.nwcUri - NWC URI identifying the wallet to use.
|
|
109
|
-
* @returns The signed NIP-47 request event and relay publish result.
|
|
110
|
-
* @throws {Error} If `nwcUri` is not provided.
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* const { event, publish } = await handleZapSend(ctx, pool, {
|
|
114
|
-
* invoice: 'lnbc500n1...',
|
|
115
|
-
* nwcUri: 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
116
|
-
* })
|
|
117
|
-
*/
|
|
118
|
-
export declare function handleZapSend(ctx: SigningContext, pool: RelayPool, args: {
|
|
119
|
-
invoice: string;
|
|
120
|
-
nwcUri?: string;
|
|
121
|
-
}): Promise<{
|
|
122
|
-
event: NostrEvent;
|
|
123
|
-
publish: PublishResult;
|
|
124
|
-
}>;
|
|
125
|
-
/**
|
|
126
|
-
* Request wallet balance via NWC.
|
|
127
|
-
*
|
|
128
|
-
* @param args - Balance request arguments.
|
|
129
|
-
* @param args.nwcUri - NWC URI identifying the wallet to query.
|
|
130
|
-
* @returns The signed NIP-47 `get_balance` request event and relay publish result.
|
|
131
|
-
* @throws {Error} If `nwcUri` is not provided.
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* const { event, publish } = await handleZapBalance(ctx, pool, {
|
|
135
|
-
* nwcUri: 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
136
|
-
* })
|
|
137
|
-
*/
|
|
138
|
-
export declare function handleZapBalance(ctx: SigningContext, pool: RelayPool, args: {
|
|
102
|
+
interface NwcOperationOptions extends NwcRequestOptions {
|
|
103
|
+
/** Injectable for deterministic tests and specialised relay transports. */
|
|
104
|
+
transport?: NwcTransport;
|
|
105
|
+
}
|
|
106
|
+
interface NwcArgs extends NwcOperationOptions {
|
|
139
107
|
nwcUri?: string;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
108
|
+
}
|
|
109
|
+
export interface ZapPaymentResult extends PayInvoiceResult {
|
|
110
|
+
paymentHash: string;
|
|
111
|
+
verified: true;
|
|
112
|
+
}
|
|
144
113
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* @param args - Invoice creation arguments.
|
|
148
|
-
* @param args.amountMsats - Invoice amount in millisatoshis.
|
|
149
|
-
* @param args.description - Optional human-readable memo attached to the invoice.
|
|
150
|
-
* @param args.nwcUri - NWC URI identifying the wallet to use.
|
|
151
|
-
* @returns The signed NIP-47 `make_invoice` request event and relay publish result.
|
|
152
|
-
* @throws {Error} If `nwcUri` is not provided.
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* const { event, publish } = await handleZapMakeInvoice(ctx, pool, {
|
|
156
|
-
* amountMsats: 21_000,
|
|
157
|
-
* description: 'Coffee ☕',
|
|
158
|
-
* nwcUri: 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
159
|
-
* })
|
|
114
|
+
* The wallet may have submitted a payment but Bray cannot prove settlement.
|
|
115
|
+
* Callers must reconcile `paymentHash` before attempting the invoice again.
|
|
160
116
|
*/
|
|
161
|
-
export declare
|
|
117
|
+
export declare class ZapPaymentOutcomeUnknownError extends Error {
|
|
118
|
+
readonly paymentHash: string;
|
|
119
|
+
readonly code = "PAYMENT_OUTCOME_UNKNOWN";
|
|
120
|
+
constructor(paymentHash: string);
|
|
121
|
+
}
|
|
122
|
+
/** Pay a BOLT-11 invoice and return only after the wallet proves settlement. */
|
|
123
|
+
export declare function handleZapSend(_ctx: SigningContext, _pool: RelayPool, args: {
|
|
124
|
+
invoice: string;
|
|
125
|
+
} & NwcArgs): Promise<ZapPaymentResult>;
|
|
126
|
+
/** Return the wallet's confirmed balance in millisatoshis. */
|
|
127
|
+
export declare function handleZapBalance(_ctx: SigningContext, _pool: RelayPool, args: NwcArgs): Promise<GetBalanceResult>;
|
|
128
|
+
/** Generate a Lightning invoice through the configured wallet. */
|
|
129
|
+
export declare function handleZapMakeInvoice(_ctx: SigningContext, _pool: RelayPool, args: {
|
|
162
130
|
amountMsats: number;
|
|
163
131
|
description?: string;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
publish: PublishResult;
|
|
168
|
-
}>;
|
|
169
|
-
/**
|
|
170
|
-
* Look up an invoice via NWC.
|
|
171
|
-
*
|
|
172
|
-
* @param args - Lookup arguments. Provide at least one of `paymentHash` or `invoice`.
|
|
173
|
-
* @param args.paymentHash - Hex-encoded payment hash of the invoice to look up.
|
|
174
|
-
* @param args.invoice - BOLT-11 invoice string to look up.
|
|
175
|
-
* @param args.nwcUri - NWC URI identifying the wallet to query.
|
|
176
|
-
* @returns The signed NIP-47 `lookup_invoice` request event and relay publish result.
|
|
177
|
-
* @throws {Error} If `nwcUri` is not provided.
|
|
178
|
-
*
|
|
179
|
-
* @example
|
|
180
|
-
* const { event, publish } = await handleZapLookupInvoice(ctx, pool, {
|
|
181
|
-
* paymentHash: 'a1b2c3...',
|
|
182
|
-
* nwcUri: 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
183
|
-
* })
|
|
184
|
-
*/
|
|
185
|
-
export declare function handleZapLookupInvoice(ctx: SigningContext, pool: RelayPool, args: {
|
|
132
|
+
} & NwcArgs): Promise<NwcTransaction>;
|
|
133
|
+
/** Look up an invoice and return the wallet's authenticated result. */
|
|
134
|
+
export declare function handleZapLookupInvoice(_ctx: SigningContext, _pool: RelayPool, args: {
|
|
186
135
|
paymentHash?: string;
|
|
187
136
|
invoice?: string;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
publish: PublishResult;
|
|
192
|
-
}>;
|
|
193
|
-
/**
|
|
194
|
-
* List recent transactions via NWC.
|
|
195
|
-
*
|
|
196
|
-
* @param args - Pagination arguments.
|
|
197
|
-
* @param args.limit - Maximum number of transactions to return (default 10).
|
|
198
|
-
* @param args.offset - Number of transactions to skip for pagination (default 0).
|
|
199
|
-
* @param args.nwcUri - NWC URI identifying the wallet to query.
|
|
200
|
-
* @returns The signed NIP-47 `list_transactions` request event and relay publish result.
|
|
201
|
-
* @throws {Error} If `nwcUri` is not provided.
|
|
202
|
-
*
|
|
203
|
-
* @example
|
|
204
|
-
* const { event, publish } = await handleZapListTransactions(ctx, pool, {
|
|
205
|
-
* limit: 5,
|
|
206
|
-
* offset: 0,
|
|
207
|
-
* nwcUri: 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
208
|
-
* })
|
|
209
|
-
*/
|
|
210
|
-
export declare function handleZapListTransactions(ctx: SigningContext, pool: RelayPool, args: {
|
|
137
|
+
} & NwcArgs): Promise<NwcTransaction>;
|
|
138
|
+
/** List recent transactions through the isolated NIP-47 extension 05 client. */
|
|
139
|
+
export declare function handleZapListTransactions(_ctx: SigningContext, _pool: RelayPool, args: {
|
|
211
140
|
limit?: number;
|
|
212
141
|
offset?: number;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
event: NostrEvent;
|
|
216
|
-
publish: PublishResult;
|
|
142
|
+
} & NwcArgs): Promise<{
|
|
143
|
+
transactions: NwcTransaction[];
|
|
217
144
|
}>;
|
|
218
|
-
|
|
219
|
-
* Parse a NWC wallet response event.
|
|
220
|
-
*
|
|
221
|
-
* @param nwcUri - The NWC URI whose secret is used to decrypt the response.
|
|
222
|
-
* @param event - A kind 23195 NIP-47 response event from the wallet service.
|
|
223
|
-
* @returns The decrypted response payload containing `result_type`, an optional
|
|
224
|
-
* `result` object, and an optional `error` with `code` and `message`.
|
|
225
|
-
*
|
|
226
|
-
* @example
|
|
227
|
-
* const response = handleZapParseResponse(
|
|
228
|
-
* 'nostr+walletconnect://abc123?relay=wss%3A%2F%2Frelay.example&secret=deadbeef',
|
|
229
|
-
* walletResponseEvent,
|
|
230
|
-
* )
|
|
231
|
-
* if (response.error) console.error(response.error.message)
|
|
232
|
-
* else console.log(response.result)
|
|
233
|
-
*/
|
|
234
|
-
export declare function handleZapParseResponse(nwcUri: string, event: NostrEvent): {
|
|
235
|
-
result_type: string;
|
|
236
|
-
result?: Record<string, unknown>;
|
|
237
|
-
error?: {
|
|
238
|
-
code: string;
|
|
239
|
-
message: string;
|
|
240
|
-
};
|
|
241
|
-
};
|
|
145
|
+
export {};
|