From b688bc409eddbec734421bab2705302c74d02398 Mon Sep 17 00:00:00 2001 From: naveenkumar29052006 Date: Fri, 23 Jan 2026 22:03:50 +0530 Subject: [PATCH 1/3] feat(utils): add preimage and payment hash helper --- examples/nwc/client/hold-invoice.ts | 13 ++++++------- src/utils.ts | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/nwc/client/hold-invoice.ts b/examples/nwc/client/hold-invoice.ts index 9eb0a939..001f05eb 100644 --- a/examples/nwc/client/hold-invoice.ts +++ b/examples/nwc/client/hold-invoice.ts @@ -1,4 +1,6 @@ import "websocket-polyfill"; // required in node.js +import { generatePreimageAndPaymentHash } from "../../../src/utils"; + import * as readline from "node:readline/promises"; import { stdin as input, stdout as output } from "node:process"; @@ -21,15 +23,12 @@ const client = new NWCClient({ nostrWalletConnectUrl: nwcUrl, }); -const toHexString = (bytes) => - bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); -const preimageBytes = crypto.getRandomValues(new Uint8Array(32)); -const preimage = toHexString(preimageBytes); +// Use shared utility instead of duplicating crypto logic + +const { preimage, paymentHash } = + await generatePreimageAndPaymentHash(); -const hashBuffer = await crypto.subtle.digest("SHA-256", preimageBytes); -const paymentHashBytes = new Uint8Array(hashBuffer); -const paymentHash = toHexString(paymentHashBytes); const response = await client.makeHoldInvoice({ amount, // in millisats diff --git a/src/utils.ts b/src/utils.ts index da46af09..724e4e5b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,19 @@ // from https://stackoverflow.com/a/50868276 -export const toHexString = (bytes: Uint8Array) => +const toHexString = (bytes: Uint8Array) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); + +async function generatePreimageAndPaymentHash() { + const preimageBytes = crypto.getRandomValues(new Uint8Array(32)); + const preimage = toHexString(preimageBytes); + + const hashBuffer = await crypto.subtle.digest("SHA-256", preimageBytes); + const paymentHash = toHexString(new Uint8Array(hashBuffer)); + + return { preimage, paymentHash }; +} + + +export { + toHexString, + generatePreimageAndPaymentHash, +}; From 96bcdbf93fd51a109166e73a01a3d420241585a1 Mon Sep 17 00:00:00 2001 From: naveenkumar29052006 Date: Fri, 23 Jan 2026 22:17:41 +0530 Subject: [PATCH 2/3] Added an explicit return --- src/utils.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 724e4e5b..45e1f574 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,13 +1,17 @@ +import crypto from "crypto"; // from https://stackoverflow.com/a/50868276 const toHexString = (bytes: Uint8Array) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); -async function generatePreimageAndPaymentHash() { - const preimageBytes = crypto.getRandomValues(new Uint8Array(32)); +async function generatePreimageAndPaymentHash(): Promise<{ + preimage: string; + paymentHash: string; +}> { + const preimageBytes = crypto.randomBytes(32); const preimage = toHexString(preimageBytes); - const hashBuffer = await crypto.subtle.digest("SHA-256", preimageBytes); - const paymentHash = toHexString(new Uint8Array(hashBuffer)); + const hashBuffer = crypto.createHash("sha256").update(preimageBytes).digest(); + const paymentHash = toHexString(hashBuffer); return { preimage, paymentHash }; } From 09b321075ed3f2b3a391a479ce436626241cca16 Mon Sep 17 00:00:00 2001 From: naveenkumar29052006 Date: Mon, 2 Feb 2026 15:47:27 +0530 Subject: [PATCH 3/3] add validation for WalletConnect connection strings --- .vscode/settings.json | 5 ++++- src/nwc/NWCClient.test.ts | 19 +++++++++++++++++++ src/nwc/NWCClient.ts | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index adbacfcb..c16685ca 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", - "prettier.trailingComma": "all" + "prettier.trailingComma": "all", + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + } } diff --git a/src/nwc/NWCClient.test.ts b/src/nwc/NWCClient.test.ts index fa430cbe..c237d53c 100644 --- a/src/nwc/NWCClient.test.ts +++ b/src/nwc/NWCClient.test.ts @@ -51,6 +51,25 @@ describe("parseWalletConnectUrl", () => { ]); }); }); +describe("parseWalletConnectUrl validation", () => { + test("throws when no relay is provided", () => { + expect(() => + NWCClient.parseWalletConnectUrl( + "nostr+walletconnect://abc123", + ), + ).toThrow(); + }); + + test("throws when secret is required but missing", () => { + expect(() => + new NWCClient({ + nostrWalletConnectUrl: + "nostr+walletconnect://abc123?relay=wss://relay.example.com", + }), + ).toThrow(); + }); +}); + describe("NWCClient", () => { test("standard protocol", () => { diff --git a/src/nwc/NWCClient.ts b/src/nwc/NWCClient.ts index cc171ffa..a18668b8 100644 --- a/src/nwc/NWCClient.ts +++ b/src/nwc/NWCClient.ts @@ -81,6 +81,37 @@ export class NWCClient { options: NWCOptions; private _encryptionType: Nip47EncryptionType | undefined; + + + + static validateWalletConnectUrl( + options: NWCOptions, + requireSecret = false, + ) { + if (!options.walletPubkey) { + throw new Error("Invalid WalletConnect URL: missing wallet pubkey"); + } + + if (!options.relayUrls || options.relayUrls.length === 0) { + throw new Error("Invalid WalletConnect URL: no relay URLs provided"); + } + + for (const relay of options.relayUrls) { + try { + new URL(relay); + } catch { + throw new Error(`Invalid relay URL: ${relay}`); + } + } + + if (requireSecret && !options.secret) { + throw new Error( + "Invalid WalletConnect URL: missing secret parameter", + ); + } + } + + static parseWalletConnectUrl(walletConnectUrl: string): NWCOptions { // makes it possible to parse with URL in the different environments (browser/node/...) // parses both new and legacy protocols, with or without "//" @@ -107,13 +138,18 @@ export class NWCClient { if (lud16) { options.lud16 = lud16; } + NWCClient.validateWalletConnectUrl(options); return options; } constructor(options?: NewNWCClientOptions) { if (options && options.nostrWalletConnectUrl) { + const parsed = NWCClient.parseWalletConnectUrl( + options.nostrWalletConnectUrl, + ); + NWCClient.validateWalletConnectUrl(parsed, true); options = { - ...NWCClient.parseWalletConnectUrl(options.nostrWalletConnectUrl), + ...parsed, ...options, }; }