Files
bitvid/docs/nwc-compliance.md
2025-10-05 20:54:04 -04:00

54 lines
3.3 KiB
Markdown

# Nostr Wallet Connect Coverage
This note cross-references Bitvid's current Nostr Wallet Connect (NIP-47) implementation with the official
specification so we can quickly spot regressions or missing features.
## URI handling
* `parseNwcUri()` accepts all aliases we see in the wild (`nostr+walletconnect://`, `walletconnect://`, and `nwc://`)
and rejects unknown schemes before continuing. It extracts the wallet pubkey, all relay query parameters, and the
32-byte secret required for signing. The helper lowercases the secret, reserializes the URI in canonical form, and
derives the client public key from the secret so downstream calls can sign events without leaking user keys.
## Connection lifecycle
* `ensureActiveState()` caches the parsed connection and keeps WebSocket state so repeated actions reuse the same
subscription. When a new URI is supplied we tear down the previous socket, update the relay list, and reset
encryption state before reconnecting.
* `connectSocket()` opens a WebSocket to the relay, subscribes to kind `23195` responses authored by the wallet and
addressed to the client pubkey, and hooks message/error/close callbacks so we can retry or surface failures.
## Request flow
* `ensureWallet()` validates that the user configured an NWC URI, connects to the relay if needed, and negotiates an
encryption scheme before returning the active context.
* `encryptRequestPayload()` serializes the JSON-RPC payload, encrypts it with the selected scheme, and creates the
Nostr event with kind `23194`, `pubkey` set to the client key, a `p` tag pointing at the wallet, and an
`encryption` tag mirroring the algorithm that was used. Events are signed with the secret from the connection URI
so relays will accept them.
* `sendPayment()` constructs the `pay_invoice` request, attaches a unique `id`, and dispatches it over the relay.
## Response flow
* `subscribeToResponses()` issues a `REQ` for kind `23195` events authored by the wallet and tagged for the client.
* `handleSocketMessage()` decrypts responses, matches them against pending `id`s (or the `e` tag), clears pending
timeouts, and surfaces either the `result` or `error.code`/`error.message` back to callers.
## Encryption negotiation
* `requestInfoEvent()` fetches the wallet's replaceable kind `13194` info note and passes the result to
`getWalletSupportedEncryption()` so we only pick schemes advertised by the wallet service.
* `getEncryptionCandidates()` prefers NIP-44 (`nip44_v2`) when both sides support it and falls back to NIP-04 when
necessary. We also remember schemes that failed so future retries skip them.
## Current gaps
* We only connect to the first relay supplied by the URI. If a wallet publishes multiple relays we should consider
retrying additional ones when the primary is offline.
* The client currently implements the `pay_invoice` command. Other optional commands (`make_invoice`,
`get_balance`, `multi_pay_invoice`, etc.) and notification handling (kinds `23196`/`23197`) would require follow-up
work.
* Wallet responses that signal `UNSUPPORTED_ENCRYPTION` as an error today bubble straight back to the caller. The
transport already retries once when we encounter that error during submission, but we could add richer UX (e.g.
prompting the user to reconnect) in the future.