From 37bec3c5e2bd6b858ea27f99a15dd03f57bdce0f Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:34:57 -0400 Subject: [PATCH] Retry NWC requests with nip04 after nip44 timeouts --- js/payments/nwcClient.js | 47 ++++++++++++++++++++++++++++++---- tests/nwc-client.test.mjs | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/js/payments/nwcClient.js b/js/payments/nwcClient.js index 5e47f4dd..cdc4d96c 100644 --- a/js/payments/nwcClient.js +++ b/js/payments/nwcClient.js @@ -673,6 +673,45 @@ async function ensureEncryption(context) { throw new Error("No compatible wallet encryption scheme available."); } +function shouldRetryWithFallback(context, error, encryption, { hasRetried }) { + if (!context || !encryption || hasRetried) { + return false; + } + + const code = typeof error?.code === "string" ? error.code : ""; + const message = typeof error?.message === "string" ? error.message : ""; + + if (code === "UNSUPPORTED_ENCRYPTION" || code === "unsupported_encryption") { + return true; + } + + const isTimeout = + message === "Wallet request timed out." || + code === "timeout" || + code === "TIMEOUT"; + + if (!isTimeout || encryption.scheme !== "nip44_v2") { + return false; + } + + const state = ensureEncryptionState(context); + if (state.unsupported.has("nip04")) { + return false; + } + + const { schemes, explicit } = getWalletSupportedEncryption(context.infoEvent); + if (explicit && schemes.includes("nip44_v2") && !schemes.includes("nip04")) { + return false; + } + + return getEncryptionCandidates(context).some((candidate) => { + if (candidate.scheme !== "nip04") { + return false; + } + return !state.unsupported.has(candidate.scheme); + }); +} + function finalizePendingRequest(entry) { if (!entry || typeof entry !== "object") { return; @@ -1067,11 +1106,9 @@ async function sendWalletRequest(context, payload, { timeoutMs, __internalRetry } catch (error) { const encryption = context?.encryption || null; if ( - !__internalRetry && - encryption && - error && - typeof error === "object" && - (error.code === "UNSUPPORTED_ENCRYPTION" || error.code === "unsupported_encryption") + shouldRetryWithFallback(context, error, encryption, { + hasRetried: __internalRetry === true, + }) ) { rememberUnsupportedEncryption(context, encryption.scheme); context.encryption = null; diff --git a/tests/nwc-client.test.mjs b/tests/nwc-client.test.mjs index ab8812b4..8bc334ac 100644 --- a/tests/nwc-client.test.mjs +++ b/tests/nwc-client.test.mjs @@ -259,4 +259,57 @@ await (async () => { resetWalletClient(); })(); +await (async () => { + resetWalletClient(); + + window.NostrTools = { + getPublicKey() { + return "c".repeat(64); + }, + nip44: { + v2: { + async encrypt(plaintext, key) { + return `nip44:${key}:${plaintext}`; + }, + async decrypt(ciphertext, key) { + return `nip44:${key}:${ciphertext}`; + }, + utils: { + getConversationKey(secretBytes, pubkey) { + return `key-${pubkey}-${secretBytes.length}`; + }, + }, + }, + }, + nip04: { + async encrypt() { + return "nip04"; + }, + async decrypt() { + return "nip04"; + }, + }, + }; + + const walletPubkey = "b".repeat(64); + const secretKey = "a".repeat(64); + const relays = ["wss://relay.one.example"]; + const uri = + `nostr+walletconnect://${walletPubkey}` + + `?relay=${encodeURIComponent(relays[0])}` + + `&secret=${secretKey}`; + + const context = ensureActiveState({ nwcUri: uri }); + context.infoEvent = { + kind: 13194, + tags: [["encryption", "nip44_v2 nip04"]], + }; + context.encryptionState.unsupported.add("nip44_v2"); + + const encryption = await __TESTING__.ensureEncryptionForContext(context); + assert.equal(encryption.scheme, "nip04"); + + resetWalletClient(); +})(); + process.exit(0);