Skip to content

nostr-core vs @getalby/sdk

This guide explains the differences between nostr-core and @getalby/sdk (the Alby JS SDK) to help you choose the right library for your project.

At a Glance

82%
fewer packages
79 vs 436
26%
smaller install
118 MB vs 159 MB
81%
fewer dependencies
132 vs 698 total
4
direct deps
All audited Noble crypto

Visual Comparison

Packages installed
nostr-core
79
@getalby/sdk
436
Install size (MB)
nostr-core
118
@getalby/sdk
159
Dependency tree
nostr-core
132
@getalby/sdk
698

Dependency Footprint

nostr-core

All production dependencies are from the Noble cryptography project - independently audited, minimal, and purpose-built:

  • @noble/curves - secp256k1 / schnorr signatures
  • @noble/hashes - SHA-256, HMAC, HKDF
  • @noble/ciphers - AES-CBC, ChaCha20
  • @scure/base - Base64, bech32 encoding

4 direct dependencies. 132 total in the dependency tree. 79 packages installed. 118 MB on disk.

@getalby/sdk

  • nostr-tools - a full Nostr client library with 50+ internal modules, many of which are unused for NWC
  • @getalby/lightning-tools - Lightning utilities including LNURL, Lightning Address resolution

698 total in the dependency tree. 436 packages installed. 159 MB on disk.

That's 5.3x more packages in the dependency tree - a proportionally larger supply chain attack surface. Every extra package is another maintainer who could push a malicious update, another dependency that could introduce breaking changes, and more code shipped to production that you never call.

Vendor Neutrality

nostr-core implements the NIP-47 protocol only. It works with any NWC-compatible wallet - Alby, Mutiny, Zeus, Coinos, or any future wallet that supports the standard.

@getalby/sdk is built around the Alby ecosystem. It ships with:

  • OAuthWebLNProvider - Alby OAuth integration
  • Alby-specific webhook handling
  • Boostagram helpers
  • Alby account management APIs

If you're not an Alby customer, this is dead code in your bundle. If you are, it creates coupling that makes switching wallets harder.

API Simplicity

nostr-core - One class, clear purpose

ts
import { NWC } from 'nostr-core'

const nwc = new NWC('nostr+walletconnect://...')
await nwc.connect()

const { balance } = await nwc.getBalance()
await nwc.payInvoice('lnbc...')

nwc.close()

@getalby/sdk - Multiple overlapping abstractions

The Alby SDK exposes several classes that overlap in functionality:

  • NWCClient - low-level NWC client
  • LNClient - high-level Lightning client with currency helpers
  • NostrWebLNProvider - WebLN wrapper around NWC
  • NWAClient - Nostr Wallet Auth for client-initiated connections
  • OAuthWebLNProvider - WebLN wrapper around Alby OAuth

Choosing the right entry point requires understanding the tradeoffs between each class.

Error Handling

nostr-core - Typed error hierarchy

NWCError (code: string)
├── NWCWalletError          - wallet rejected the request
├── NWCTimeoutError         - generic timeout
│   ├── NWCPublishTimeoutError  - relay didn't acknowledge
│   └── NWCReplyTimeoutError    - wallet didn't respond
├── NWCPublishError         - relay rejected the event
├── NWCConnectionError      - couldn't connect to relay
├── NWCDecryptionError      - couldn't decrypt response
├── LightningAddressError   - Lightning Address resolution failed
└── FiatConversionError     - fiat-to-sats conversion failed

This lets you handle each failure mode precisely:

ts
import { NWCWalletError, NWCPublishTimeoutError, NWCReplyTimeoutError } from 'nostr-core'

try {
  await nwc.payInvoice('lnbc...')
} catch (err) {
  if (err instanceof NWCWalletError) {
    // Wallet said no - show the reason to the user
    showError(`Wallet rejected: ${err.message} (${err.code})`)
  } else if (err instanceof NWCPublishTimeoutError) {
    // Relay is down - try a different relay
    retryWithBackup()
  } else if (err instanceof NWCReplyTimeoutError) {
    // Wallet is offline - tell the user to check their wallet
    showError('Wallet appears offline')
  }
}

@getalby/sdk - Generic errors

Errors are less structured, making it harder to distinguish between relay issues, wallet rejections, and timeout scenarios in your error handling logic.

Encryption Detection

nostr-core handles encryption automatically:

  1. On connect(), it queries the wallet's info event (kind 13194)
  2. Checks for encryption or v tags to determine NIP-04 vs NIP-44 support
  3. Falls back to NIP-04 if no info event is found
  4. All subsequent requests use the correct encryption - zero configuration needed

Cross-Runtime Support

nostr-core works anywhere with Web Crypto and WebSocket APIs:

  • Node.js 18+
  • Deno
  • Bun
  • Cloudflare Workers

This is possible because the library depends only on standard Web APIs and the Noble crypto libraries (which are pure JavaScript with no native bindings).

Full NIP-47 Coverage

All methods supported

With 81% fewer dependencies, nostr-core still implements every NIP-47 method.

Methodnostr-core@getalby/sdk
pay_invoiceYesYes
get_balanceYesYes
make_invoiceYesYes
get_infoYesYes
get_budgetYesYes
list_transactionsYesYes
lookup_invoiceYesYes
pay_keysendYesYes
sign_messageYesYes
Notifications (payment_received, payment_sent)YesYes

Low-Level Building Blocks

nostr-core exports its internals so you don't need a separate nostr-tools install:

  • Key management: generateSecretKey, getPublicKey
  • Events: finalizeEvent, verifyEvent, getEventHash
  • Relay connections: Relay, RelayPool
  • Encryption: nip04, nip44
  • Encoding: nip19 (bech32)
  • Filtering: matchFilter, matchFilters

Lightning Address Support

nostr-core natively resolves Lightning Addresses via LNURL-pay - no extra dependencies needed:

ts
// One-liner: resolve address + pay the invoice
const { preimage, invoice } = await nwc.payLightningAddress('hello@getalby.com', 100)

// Or resolve separately without paying
import { fetchInvoice } from 'nostr-core'
const { invoice, metadata } = await fetchInvoice('hello@getalby.com', 100)

This eliminates the need for @getalby/lightning-tools or any external LNURL library.

Fiat Currency Conversion

nostr-core includes built-in fiat-to-sats conversion using public exchange rate APIs (CoinGecko), with automatic 60-second rate caching:

ts
import { fiatToSats, satsToFiat, getExchangeRate } from 'nostr-core'

// Convert fiat to sats
const { sats, rate } = await fiatToSats(5, 'usd')
console.log(`$5 = ${sats} sats (at $${rate}/BTC)`)

// Convert sats to fiat
const { amount } = await satsToFiat(10000, 'eur')
console.log(`10,000 sats = €${amount.toFixed(2)}`)

// One-liner: pay a Lightning Address in fiat
const result = await nwc.payLightningAddressFiat('hello@getalby.com', 5, 'usd')

This eliminates LNClient from @getalby/sdk as a reason to use the Alby SDK.

When to Use @getalby/sdk

@getalby/sdk is the right choice when you need:

  • Alby OAuth integration - authenticating with Alby accounts
  • WebLN compatibility - implementing the WebLN provider interface
  • Alby-specific features - webhooks, boostagrams, account management

When to Use nostr-core

nostr-core is the right choice for everything else:

  • You want a vendor-neutral NWC client
  • You care about install size (26% smaller) and dependency count (82% fewer packages)
  • You need cross-runtime support (Deno, Bun, Workers)
  • You want typed errors for precise failure handling
  • You're building a product that should work with any NWC wallet
  • You want to minimize your supply chain attack surface (81% fewer dependencies in the tree)

Released under the MIT License.