nwc-kit

NIP-47 · NIP-44 v2 · ESM

Spend from someone else's wallet
without trusting the wire.

nwc-kit is a small Nostr Wallet Connect client for TypeScript. It lets an application use a constrained connection to an existing Lightning wallet. It is not a wallet, a payment rail, a custody layer, a policy engine or an invoice verifier, and it is careful to stay that way.

npm install @forgesworn/nwc-kit Watch it reject a forged wallet
  • One runtime dependency
  • Node 22+, browsers, Deno, Bun
  • MIT
  • 0.x, API not frozen

Early release. Verified against the NIP-44 protocol vectors and the adversarial wallet below, but not yet against a real wallet service in the field. Pin an exact version.

The relay is untrusted

Subscription filters are performance hints, not authentication. Every response is shape-checked, signature-verified and matched to the live request before anything is decrypted. A valid wallet event that does not reference the active request is treated as a possible replay and ignored rather than allowed to fail the request.

NIP-44 v2 or nothing

Legacy NIP-04 is refused, not merely deprecated. The client requires the wallet to advertise nip44_v2 in a signed capability event before it will send anything at all, and it checks the wallet author, request reference, client recipient and result method before trusting a result.

A timeout is not a refund

Once publication begins, timeout, abort, close and publish failure are ambiguous outcomes. A relay can store an event without returning a usable acknowledgement. The library says so, the types say so, and the docs say so, because a blind retry can pay twice.

Live demo

This page runs the real library. In simulated mode a wallet lives in the page, signs its own Nostr events and encrypts to you with real NIP-44 v2, so the client cannot tell it from a remote one. Change how that wallet behaves and watch the client accept or reject it.

Demo wallet balance 0 sats

Nothing leaves your browser in this mode. There is no relay and no network request.

Protocol tape Ready.

Press Connect. Every event below is really signed, really encrypted and really verified by the library you would install.

The whole API

Two exports. The draft extension is kept behind the second one so the core cannot come to depend on it.

Paying, carefully

import { NwcClient } from '@forgesworn/nwc-kit'
import { tryDecodeBolt11, verifyPreimage } from 'farrier-kit'

const client = new NwcClient(connectionUri)

try {
  const caps = await client.connect()
  if (!caps.methods.includes('pay_invoice')) {
    throw new Error('This connection cannot pay')
  }

  const decoded = tryDecodeBolt11(invoice)
  if (!decoded || decoded.amountMsats === null) {
    throw new Error('Refusing an amountless invoice')
  }

  const result = await client.payInvoice({ invoice })

  if (!verifyPreimage(result.preimage, decoded.paymentHashHex)) {
    throw new Error('Response does not settle this invoice')
  }
} finally {
  client.close()
}

Surface

connect()Signed capability discovery
payInvoice()Returns a validated preimage
makeInvoice()Amount in milli-satoshis
lookupInvoice()By payment hash or invoice
getBalance()Milli-satoshis
getInfo()Wallet metadata
close()Cancels work, zeroises keys
inspectNwcConnection()Reads a URI without exposing the secret

All amount, balance and fee fields are integers in milli-satoshis. Convert sats explicitly at the application boundary and reject ambiguous amounts before making a request.

Transaction history is draft extension 05 and lives at @forgesworn/nwc-kit/extensions/05. It always sends an explicit page limit, capped at 20, because a wallet asked for no particular page size applies a larger default of its own.

What it refuses to do

Not in scope, deliberately

  • Wallet or node implementation
  • Custody, balances, subwallet management
  • BOLT-11, LNURL, L402, Cashu, WebLN, fiat
  • Amount policy and user approval
  • Settlement verification
  • Legacy NIP-04 interoperability

What you still owe your users

The wallet service is authoritative for whether it attempted a payment, but its response is not settlement proof. Verify the invoice before you pay it and verify the returned preimage against the invoice payment hash afterwards. farrier-kit does both.

Wallet-enforced permissions and budgets are defence in depth, not a replacement for your own approval and spend policy. Passing tests are not evidence that money settled.