Buho-Ecosystem

One Class, One Connection String

The entire NWC spec in five lines of code. That's not a simplification; it's the actual API.

DrShift

August 17, 2026 · 4 min read

Payment Integration Shouldn't Be Hard

Every payment integration story starts the same way. You read the docs. You install the SDK. You create an account. You get API keys. You configure webhooks. You handle OAuth flows. You write error handling for seventeen different failure modes.

By the time you've sent your first payment, you've written more infrastructure code than product code.

NWC was designed to be simpler than that. And nostr-core takes that simplicity seriously.

Five Lines#

import { NWC } from 'nostr-core'

const nwc = new NWC('nostr+walletconnect://...') await nwc.connect() const { preimage } = await nwc.payInvoice('lnbc...') nwc.close()

That's a complete payment. Import, connect, pay, close.

The connection string contains everything: the wallet's public key, the relay to communicate through, and your secret key. No separate config files. No environment variables for client IDs and secrets. One string.

What the NWC Class Handles#

Behind those five lines, nostr-core is doing real work.

Encryption auto-detection. Some wallets use NIP-04 (AES-CBC). Others support NIP-44 (ChaCha20). nostr-core checks what the wallet supports and uses the best available option. You don't configure this. It just works.

Relay management. The class connects to the relay specified in the connection string, subscribes to the right filters, and handles the WebSocket lifecycle. Reconnection, subscription management, cleanup on close.

Request/response matching. NWC is asynchronous over Nostr relays. nostr-core matches each response to its request, handles timeouts, and surfaces errors through a typed hierarchy.

Event signing. Every NWC request is a signed Nostr event. nostr-core handles the signing, serialization, and verification internally.

You don't see any of this. You see payInvoice() and get a preimage back.

The Full Surface#

The NWC class exposes every NIP-47 method:

  • getBalance() - how much is in the wallet

  • payInvoice() - pay a BOLT-11 invoice

  • makeInvoice() - create an invoice to receive

  • listTransactions() - payment history

  • payKeysend() - pay a node directly

  • payLightningAddress() - resolve and pay a Lightning address

  • payLightningAddressFiat() - convert fiat to sats and pay

  • getInfo() - wallet metadata

  • getBudget() - spending limits

  • signMessage() - sign with the wallet key

Plus event listeners for real-time payment notifications:

nwc.on('payment_received', (notification) => { console.log('Incoming:', notification.notification.amount, 'msats') })

That's the full wallet API. One class, one import.

Errors That Tell You What Happened#

When something goes wrong, you get a specific error, not a generic exception with a string message.

try { await nwc.payInvoice('lnbc...') } catch (err) { if (err instanceof NWCWalletError) { // The wallet said no: insufficient balance, expired invoice, etc. } else if (err instanceof NWCTimeoutError) { // Wallet didn't respond in time } else if (err instanceof NWCConnectionError) { // Couldn't reach the relay } }

Eight error classes in a clean hierarchy. Your error handling can be as specific or as general as you need.

Lightning Addresses in One Call#

Lightning Address resolution normally means fetching a well-known URL, parsing the LNURL response, creating an invoice, then paying it. nostr-core wraps that entire flow:

await nwc.payLightningAddress('user@example.com', 1000)

One thousand sats to a Lightning address. One line.

Need fiat conversion? That's built in too:

await nwc.payLightningAddressFiat('user@example.com', 5, 'usd')

Five dollars, converted to sats at the current rate, paid to a Lightning address. Still one line.

The DX Argument#

Developer experience isn't about making things pretty. It's about reducing the distance between your intention and your code.

If you want to pay an invoice, the code should say "pay this invoice." If you want to listen for payments, the code should say "when a payment arrives, do this." The protocol complexity should be invisible unless you need to see it.

That's what one class and one connection string gives you. The intent is the code.


Start with npm install nostr-core and a connection string. That's all you need.

GitHub · API Docs


Originally published at https://nostr-core.netlify.app/blog/02-one-class-one-connection-string.html

0

Written by

Spreading Light ⚡

drshift@lnbits.de

Comments0

No comments yet. Start the conversation.

About

Buho-Ecosystem

Stay connected with everything happening across the Buho Ecosystem. Discover the latest news, product updates, release notes, development stories and community highlights from all Buho applications in one place. Whether you are following BuhoGO, exploring new ecosystem tools like Lotus or simply curious about what we are building next, this is where we share progress, ideas and the stories behind our products. Follow the journey, discover new features and see how the Buho Ecosystem continues to grow.

Topics

Subscribe

Follow DrShift on Nostr and new posts land in your Subscribed feed.

Buho-Ecosystem · Powered by EINUNDZWANZIG