Community

Apps

nwcjs

This is not a wallet and not a hardened payment SDK. It is a compact browser JavaScript helper that parses NWC pairing strings, signs Nostr events, talks to relays over WebSockets, sends NIP-47 wallet requests and includes experimental zap helpers.

nwcjs visual
nwcjs icon
Apps The product layer Clients, signers, publishing tools, wallets and useful experiments.
Back to Nostr
Apps

Apps shelf

Apps pages collect clients, signers, tools, developer libraries and product research without turning the app into the whole network.

Apps28 min readSmall vanilla JavaScript helper for Nostr Wallet Connect and zap experiments

nwcjs

This is not a wallet and not a hardened payment SDK. It is a compact browser JavaScript helper that parses NWC pairing strings, signs Nostr events, talks to relays over WebSockets, sends NIP-47 wallet requests and includes experimental zap helpers.

The quick readnwcjs is a tiny vanilla JavaScript library by Super Testnet for working with Nostr Wallet Connect and zaps in the browser. The repository is `supertestnet/nwcjs`, CC0 licensed, not archived, created in May 2024 and pushed as recently as March 2026 when checked on June 14, 2026. It is not an npm package in the usual sense. The README tells you to import `noble-secp256k1`, `bech32` and the hosted `nwcjs.js` file with script tags. The code parses a `nostr+walletconnect://` URI, derives the app pubkey from the secret, signs kind 23194 request events, opens relay WebSockets, waits for kind 23195 responses and exposes helpers such as `getInfo`, `makeInvoice`, `checkInvoice`, `getBalance`, `listTransactions`, `tryToPayInvoice`, `didPaymentSucceed`, `makeHodlInvoice`, `settleHodlInvoice`, `cancelHodlInvoice`, `getZapRequest` and `checkZapStatus`. The tradeoff is exactly its charm: it is easy to read, but you must audit it before production. It currently uses NIP-04-style AES-CBC encryption rather than NIP-44, stores connection objects in a global array, has a global response queue, waits by delay tolerance instead of robust subscription lifecycle management and the zap helper code should be tested carefully because the current source appears to reference an `lnurl` variable after commenting out its assignment.

Start with the shape

nwcjs is deliberately small. It is a vanilla JavaScript file for working with Nostr Wallet Connect and zaps, published by Super Testnet as a GitHub repository and a GitHub Pages script. You import it with script tags, not through a full framework or build system.

That makes it different from the larger Alby SDK path and different from a backend HTTP bridge. nwcjs wants to be readable browser code. It parses an NWC pairing string, signs Nostr events, opens WebSocket connections to relays, sends wallet requests and waits for responses.

The repository metadata checked on June 14, 2026 showed `supertestnet/nwcjs` as a CC0-licensed project, not archived, created in May 2024 and pushed in March 2026. It had a small footprint: a README, `nwcjs.js`, an index page that imports the script and a license file.

That size is the whole product signal. This is a tool for builders who want to understand or prototype NWC in plain browser JavaScript. It is not a polished, versioned, audited payment SDK with package manager releases, semantic versioning and production support guarantees.

How you import it

The README shows three script imports: `noble-secp256k1` from bundle.run, `bech32` from bundle.run and `nwcjs.js` from Super Testnet's GitHub Pages URL. The local `index.html` in the repository is almost empty except for those script tags.

That is convenient for learning. You can drop the imports into a static page and experiment with a wallet connection without creating a bundler setup. It also means the supply chain is simple to see: your page depends on the hosted crypto library, the hosted bech32 library and the hosted nwcjs file.

For production, that import model deserves a decision. Loading payment code from public CDNs and GitHub Pages gives you fast iteration, but it also means runtime code can change outside your deploy process. A serious app should pin, self-host, integrity-check or vendor the exact files it reviewed.

This is not a criticism of the project. It is the normal difference between a learning library and a payment surface. The first is allowed to be small and direct. The second needs reproducible assets, dependency review, update policy and a rollback story.

Parsing the NWC string

The first important function is `processNWCstring`. It expects a string that starts with `nostr+walletconnect://`. It extracts the wallet pubkey, relay, secret and optional extra fields, renames `secret` to `app_privkey` and derives `app_pubkey` with `nobleSecp256k1.getPublicKey`.

The resulting object is also pushed into `nwcjs.nwc_infos`, a global array the README mentions as a way to manage more than one NWC connection. That is useful for demos, because you can hold several parsed connections in memory and call helpers against each one.

The security implication is direct. The parsed object contains the app private key for the NWC connection. If you store that object in a global variable, it is accessible to the page context. If another script on the same page is compromised, the NWC connection can be compromised within its wallet permissions.

The parser also does only simple percent replacement for the relay value. That may be enough for the README example, but a production parser should use structured URL parsing, handle multiple relays and parameters carefully, reject malformed strings without alert side effects and avoid accidentally logging the secret.

NIP-47 in plain JavaScript

Once it has a parsed connection, nwcjs builds NIP-47 request events. It creates a JSON message with a method and params, encrypts the message to the wallet pubkey, wraps it as kind 23194, adds a `p` tag for the wallet service and signs the event with the app private key.

The library includes a `getSignedEvent` helper that serializes the event according to the Nostr event ID rules, hashes it with SHA-256 and signs the ID with Schnorr. It also includes `sendEvent`, which opens a WebSocket to the relay, sends a Nostr `EVENT` message and closes the socket shortly after.

For responses, `getResponse` queries the relay for kind 23195 events tagged with the original request event ID and the app pubkey. It waits for a response event, decrypts its content and pushes the parsed result into a global response array.

That is enough to teach the NWC flow clearly. A reader can see the event kinds, pubkeys, tags, relay subscription and encrypted content path in one file. The same clarity also shows what a production library would need to harden: connection reuse, robust socket lifecycle, concurrency, retries, backoff, multiple relays, subscription closure and error typing.

The encryption choice

The current code implements encryption with a secp256k1 shared secret and browser `window.crypto.subtle` AES-CBC, returning a base64 ciphertext plus an IV query fragment. That is the old NIP-04 style of encrypted direct message content.

NIP-47 has moved toward NIP-44 as the preferred encryption scheme, with NIP-04 kept as a deprecated compatibility path. That matters because many older NWC examples still work through NIP-04, while newer wallets and clients may advertise or expect NIP-44 support.

A builder using nwcjs should check the target wallet service. Does it accept NIP-04 NWC requests? Does it require NIP-44? Does its kind 13194 info event advertise encryption methods? If the wallet service changes, a small helper that assumes one encryption mode may fail quietly or time out.

For learning, the code is useful because it makes encryption visible. For production, you should either add NIP-44 support, constrain your wallet compatibility list or choose a maintained library whose encryption behavior tracks the current NIP-47 specification.

Invoice and balance helpers

The README demonstrates the core wallet helpers. `getInfo` asks the wallet service for supported methods. `makeInvoice` creates a Lightning invoice by sending a `make_invoice` request with an amount converted from sats to millisats and a description. `checkInvoice` looks up a BOLT11 invoice by sending `lookup_invoice`.

The library also exposes `getBalance` and `listTransactions`. The transaction helper accepts filters for `from`, `until`, `limit`, `offset`, unpaid transactions and direction. Those are powerful methods, and they reveal why connection permissions matter.

A browser app that only needs to pay one invoice probably should not need balance or transaction history. A receiving app may need `make_invoice` but not `pay_invoice`. The library can call several methods, but the wallet connection should decide which ones are actually authorized.

When testing, do not stop at a successful invoice. Check what happens when the wallet service rejects a method, when the balance is insufficient, when the relay response arrives late, when no response arrives, and when a wallet returns an error object instead of a result.

Payment is intentionally pending

One of the most thoughtful parts of the README is the rename from `payInvoice` to `tryToPayInvoice`. The README explains that Lightning payments do not reliably succeed immediately and can get stuck for minutes or hours, so the method sends the pay command without waiting forever for a preimage.

The code reflects that. `tryToPayInvoice` creates a `pay_invoice` request and sends it to the relay, but it does not wait for a response in the same style as `makeInvoice` or `getBalance`. The README recommends following up with `didPaymentSucceed`, which calls `checkInvoice` and returns the preimage if payment completed.

That naming is good product thinking. A wallet request is not the same as a settled payment. A user-facing app should show pending state, let the user retry carefully, and avoid assuming that a sent request means the merchant, stream, post, AI task or account credit should already be fulfilled.

The practical warning is that pending logic now becomes your app's job. If you build on nwcjs, store the invoice, check status, handle timeouts, avoid double fulfillment and explain the state to the user. A small helper can send the payment request; it cannot design your settlement workflow.

HODL invoice support

The code includes HODL invoice helpers: `makeHodlInvoice`, `settleHodlInvoice` and `cancelHodlInvoice`. These are not shown prominently in the README examples, but they are present in the source and use the same encrypted kind 23194 request pattern.

HODL invoices are a more advanced Lightning flow. They let a receiver create an invoice whose final settlement depends on a preimage decision later. That can be useful for escrow-like flows, conditional fulfillment or services that need to control settlement after another event happens.

The risk is that advanced flows require advanced testing. You need to know whether the wallet service supports these methods, how expiry works, what happens after partial failure, how cancellation is represented and whether the response semantics match your business logic.

For most nwcjs users, the safest first path is ordinary invoice creation or ordinary invoice payment. HODL flows are worth documenting because the code supports them, but they should not be enabled casually in a product with real users and no recovery process.

The zap helpers need extra care

nwcjs also includes `getZapRequest` and `checkZapStatus`. The README says `getZapRequest` takes a Lightning Address and amount, creates a zap request and returns a Lightning invoice plus a checking ID. By default it asks for zap receipts on `wss://nostrue.com`.

The code fetches the recipient's LNURL-pay metadata from `/.well-known/lnurlp/`, reads `nostrPubkey`, creates a temporary keypair, builds a kind 9734 zap request, calls the LNURL callback and returns the returned invoice. `checkZapStatus` then looks for a kind 9735 zap receipt tagged with the checking ID and matching BOLT11 invoice.

This is useful as a compact explanation of NIP-57 mechanics, but it should be tested before use. In the current source checked on June 14, 2026, the line that computes an `lnurl` value appears commented out, while the function later uses `lnurl` in the zap request tags and callback URL. That looks like a bug or unfinished edit.

So the honest advice is simple: use the zap code as readable reference material, not as a trusted drop-in zap implementation. Confirm the `lnurl` handling, validate the LNURL metadata, support multiple receipt relays, check amount units and verify the zap receipt before you show a user that a zap succeeded.

Delay tolerance is not reliability

The README repeatedly says that if there is an error, you can increase delay tolerance from three seconds to five or so. The code's relay query helper waits in one-second increments until a response arrives or the delay tolerance is reached.

That is fine for a tutorial and for simple demos. It is not the same as robust relay reliability. Nostr relays can be slow, offline, rate limited or filtered. Wallet services can answer on a different relay. A user's browser can sleep. Mobile networks can flap. A fixed delay window will sometimes be wrong.

A production app should treat the delay tolerance as a starting point, not a guarantee. It should show pending state, allow manual refresh, retry subscriptions when appropriate, avoid infinite recursion, and maybe maintain longer-lived relay connections for the workflows where a delayed response is normal.

This is another place where nwcjs is useful because it makes the tradeoff visible. You can see exactly how long the demo waits and what happens when no event arrives. From there, you can decide whether to extend it or move to a stronger client library.

Global state is convenient and risky

nwcjs keeps `nwc_infos` and `response` as arrays on the global `nwcjs` object. That makes the library easy to use from a browser console or simple static page. It also means concurrency and secret handling are your responsibility.

The response flow filters responses by result type after `getResponse` has already selected by event ID and app pubkey. That works for simple sequential examples. If a page fires many concurrent requests across several connections, a global response array can become harder to reason about.

The connection array is more sensitive because it stores parsed NWC connection objects. If an app persists those objects, serializes them to local storage or exposes them to other scripts, it can leak the app private key. The library does not force a storage model, so the app must choose one deliberately.

For safe experiments, use a dedicated low-budget NWC connection and keep it short-lived. For production, wrap the library with your own connection manager, isolate secrets, avoid unnecessary global access and make concurrent request handling explicit.

Where it fits beside other tools

nwcjs sits between raw protocol learning and full SDK adoption. It is more concrete than reading NIP-47 alone because it signs events, opens WebSockets and sends real requests. It is smaller than Alby's SDK and less managed than Alby's NWC HTTP API.

If you want a browser demo that teaches how NWC works, nwcjs is attractive. You can read one file and understand the important path. If you want a production app with modern encryption negotiation, packaging, tests, typed errors and broad wallet support, you will probably want a maintained SDK or a hardened fork.

It is also connected to other Super Testnet experiments. The NWC tester pages in the same ecosystem use simple browser-based NWC flows, and Bankify has historically warned users that it stores an NWC connection string in local storage and uses nwcjs under the hood. That is exactly the kind of experimental context this library belongs to.

That context is not a problem as long as the reader sees it. nwcjs is a hands-on building block. It is not a stamp that a wallet integration is safe by default.

Protocol gaps to watch

The current NIP-47 specification is not frozen forever. It has moved toward NIP-44 encryption, defines info events and notifications, and expects clients and wallet services to negotiate capabilities rather than assume everything. A compact library can drift from the latest spec faster than a larger maintained SDK.

The visible gaps to watch in nwcjs are specific. It uses NIP-04-style encryption. It does not appear to fetch kind 13194 wallet-service info before using methods in the helper calls. It treats one relay from the parsed string as the target. It uses delay tolerance instead of a richer subscription lifecycle.

The library also includes methods that may not be supported by every wallet service. `make_hodl_invoice`, `settle_hodl_invoice` and `cancel_hodl_invoice` are useful only when the wallet behind the connection implements them. `list_transactions` and `get_balance` require permissions that a narrow user connection may not grant.

Your test matrix should be wallet-specific. Try Alby Hub, Coinos, LNbits, Zeus or whichever NWC wallet you intend to support. Check accepted methods, encryption, response timing, errors, amount units, relay paths and revocation behavior. A small demo success with one wallet is not universal compatibility.

How to test it safely

Start with a disposable NWC connection. Use a wallet profile with a tiny budget, a short expiry and only the methods you need. Do not paste a main wallet connection into a random browser page while learning.

Load a pinned local copy of the script and its dependencies. Parse the pairing string. Call `getInfo` first if your wallet service supports it. Then make a tiny invoice, look it up, pay a tiny test invoice and call `didPaymentSucceed` until you understand the pending behavior.

Test error cases deliberately. Use an unsupported method, a revoked connection, a wrong relay, a malformed invoice, an amountless invoice with and without amount override, a slow relay and a wallet with insufficient funds. Watch what the library returns and what your UI would show.

Only after those tests should you consider zaps. Fix or verify the `lnurl` path, request a tiny zap, inspect the kind 9734 request, pay the invoice, fetch the kind 9735 receipt and confirm the BOLT11 tag matches. Zaps have more moving parts than simple NWC invoice payment.

Who should use it

nwcjs fits educators, tinkerers and builders who want to see NWC in the open. It is especially useful when a developer wants to understand the event construction rather than hide it behind an SDK abstraction.

It also fits prototypes where the wallet risk is intentionally tiny. A demo checkout, a workshop, a test page, a zap experiment or an internal proof of concept can benefit from a small file that is easy to inspect and modify.

It does not fit high-value payments, unattended production wallets or apps that cannot afford NWC compatibility surprises. If you need audited dependencies, NIP-44 support, multi-relay resilience, strict secret isolation and typed request state, treat nwcjs as reference code and build or choose something stronger.

The healthiest use is layered: read nwcjs to understand the protocol, test with small budgets, then either harden it carefully or graduate to a maintained SDK once the product needs reliability beyond a readable demo.

What to pin before production

If you do decide to build from nwcjs, start by pinning the exact source you reviewed. The README import path points to GitHub Pages, which is friendly for learning but not a deployment lock. Copy the reviewed file into your own app, keep a checksum beside it and make updates intentional.

Do the same for the dependencies. `noble-secp256k1` and `bech32` are loaded from bundle.run in the README example. A real wallet surface should know which dependency versions it is loading, where they come from and how an emergency rollback would work if a hosted file or dependency behavior changes.

Then wrap the library instead of exposing it raw. A small wrapper can enforce allowed methods, hide the parsed NWC object, serialize payment attempts, store request IDs, normalize errors and prevent unrelated page code from reading connection material. That wrapper is where the prototype becomes an application boundary.

Finally, write down the compatibility promise. If your product only supports wallets that accept NIP-04 NWC requests today, say that internally and in your tests. If you plan to move to NIP-44, track that work explicitly. Silent protocol drift is how small payment helpers become brittle.

The practical close

nwcjs is valuable because it makes Nostr Wallet Connect feel tangible. A single browser file parses the pairing string, signs events, talks to relays, asks a wallet for invoices or payments and shows how zaps can be assembled from LNURL and Nostr events.

Its limits are equally visible. It uses a simple import model, global state, delay-based relay waiting, older encryption style and experimental zap code that needs inspection. Those choices make it easy to learn from and risky to deploy blindly.

Use it with the right expectations. Pin the source, protect the NWC secret, keep permissions tiny, test wallet compatibility, do not treat payment requests as final settlement, and verify zap receipts before showing success.

In that lane, nwcjs earns its place in the developer stack. It is not the big SDK. It is the small readable bridge that helps you see exactly what NWC is doing before you decide how much of it your product should own.

Useful Nostr context

These pages are useful when comparing a tiny browser helper with maintained SDK, HTTP and protocol paths.

Sources worth opening

Open the README and source file first, then compare NIP-47, NIP-04, NIP-44 and NIP-57 so the library's compact browser choices are clear against the current protocol documents.

Back to the Crays Nostr page
Apps route visual cue 1
Apps route visual cue 2
Apps route visual cue 3
Apps route visual cue 4
Apps route visual cue 5

How to use this page

Keep the product map close.

Search the wider app shelf when you want another client, tool, protocol reference or source trail beside this page.

AppsBrowse Apps pagesApps pages stay availableProducts, tools, communities and source trails.Browse pages
Apps contribution visual cue 1
Apps contribution visual cue 2
Apps contribution visual cue 3
Apps contribution visual cue 4
Apps contribution visual cue 5

Bring something back

Ask, suggest, submit or nominate.

Ask a question, send a source, suggest a fix, submit a project or nominate a public Nostr account. The page stays stable; your contribution gets reviewed beside it.