@coinexams/lightning
v1.1.4
Published
Self-custodial Bitcoin Lightning Network client for Node.js — invoices, payments, balances, LNURL-pay addresses and Nostr Zap receipts via a local Phoenixd node.
Readme
@coinexams/lightning
A self-custodial Bitcoin Lightning Network client for Node.js that wraps a local Phoenixd node REST API — create invoices, send Lightning and on-chain payments, query balances and history, resolve LNURL-pay requests, and publish Nostr Zap receipts.
Table of Contents
- Why @coinexams/lightning
- Install
- Quick Start
- Configuration
- Exports
- Client methods
- Invoices
- Payments
- Nostr Zaps
- Error handling
- CLI setup
- FAQ
- Keywords
Why @coinexams/lightning
- Self-custodial — talks only to your own local Phoenixd node over
localhost; no third-party custodian. - Zero protocol plumbing — invoices, payments, LNURL-pay, and Nostr kind-9735 Zap receipts behind one small client.
- TypeScript first — every request, response, and enum is typed and shipped as
dist/index.d.ts. - Nostr native — sign Zap receipts, resolve sender relays, and publish to a preset relay list out of the box.
- Resilient — invoice creation auto-recovers a stalled node, request timeouts are capped, and concurrent restarts are coordinated.
- Tiny surface — a single
startLightning()factory plus a handful of helpers, with no runtime framework lock-in.
Install
npm install @coinexams/lightningyarn add @coinexams/lightningpnpm add @coinexams/lightningNode.js
>=18. This is a Node-only package (it usesnode:child_processandBuffer); there is no browser bundle or CDN build.
Quick Start
1. Setup Phoenixd
Install, configure, and start a local Phoenixd node as a systemd service (requires Linux with systemd and sudo):
npx @coinexams/lightning setup
# Optional: --seed "<12-or-24-word phrase>" --port 9740On success the CLI prints the ready port and persists the credentials Phoenixd generated.
2. Connect and pay
import { startLightning, nsecToBytes, npubToHex } from "@coinexams/lightning";
const client = startLightning({
serverPrvKeyBytes: nsecToBytes("nsec1..."),
serverPubKeyHex: npubToHex("npub1..."),
});
// Full zap workflow: build an invoice, poll until paid, publish a kind-9735 receipt
const zap = client.zapProcess({
lnAddress: `[email protected]`,
amountMsat: 100_000,
nostrEvent: `<zap-request-json>`,
});
// → { invoice: { pr: "lnbc...", routes: [] }, invoiceId: "..." }Configuration
startLightning(options) returns a LightningClient, or undefined when the Phoenixd credentials cannot be loaded.
| Option | Type | Default | Description |
| ------------------- | ------------ | ------------- | ------------------------------------------------------------------ |
| serverPrvKeyBytes | Uint8Array | — | Server Nostr private key used to sign Zap receipts (kind 9735). |
| serverPubKeyHex | string | — | Server Nostr public key in hex, set as the receipt pubkey. |
| defaultRelays | string[] | RELAYS_LIST | Extra relays used when a Zap request carries no relays tag. |
| cacheDurationMs | number | 500 | Cache window for read-only fundsData responses, in milliseconds. |
Reads http-password and http-bind-port from the Phoenixd config on disk, so the node must be installed and running before calling startLightning.
Exports
Functions and values
| Export | Signature | Returns | Description |
| -------------------------------------- | ---------------------------------------- | -------------------------- | ---------------------------------------------------------------------------- |
| startLightning | (options) => LightningClient | LightningClient \| undefined | Create a client bound to the local Phoenixd node. |
| msatToSat | (msat) => number | number | Convert millisatoshis to satoshis; 0 on invalid input. |
| isLightningAddress | RegExp | — | Matches user@domain Lightning addresses. |
| RELAYS_LIST | string[] | — | Preset list of Nostr relay URLs used as the default for defaultRelays. |
| npubToHex | (npub) => string | string | Convert an npub/nprofile bech32 key to hex; empty string on failure. |
| nsecToHex | (nsec) => string | string | Convert an nsec bech32 key to a hex private key; empty string on failure. |
| hexToBytes | (hex) => Uint8Array | Uint8Array \| undefined | Convert a hex string to bytes; undefined on failure. |
| nsecToBytes | (nsec) => Uint8Array | Uint8Array \| undefined | Convert an nsec bech32 key directly to bytes; undefined on failure. |
Enums
| Export | Members | Description |
| ------------------ | ------------------------------------------------------------ | ----------------------------------------------------------------- |
| PaymentDirection | Incoming ("incoming"), Outgoing ("outgoing") | Selects the payment direction for invoiceStatus. |
| LNDataType | GetBalance, GetInfo, PaymentsIncoming, PaymentsOutgoing | Common Phoenixd endpoints for fundsData type values. |
Types
IncomingPayment, OutgoingPayment, SentPayment, LNBalance, PaymentInvoiceDetails, PaymentMakeRequest, PaymentNewRequest, LNDataParams, ZapRequest, ZapRequestResponse, ZapSignRequest, ZapSignResponse, ZapPublishRequest, UserStoredPayment, UserSeverData.
Client methods
All methods return undefined on failure and log via console.error with an ISO timestamp prefix. Optional maxTime (seconds) caps the underlying curl request; it defaults to 1 second.
| Method | Signature | Returns | Description |
| --------------- | ------------------------------------------------------------------ | ---------------------------------------- | ----------------------------------------------------------------- |
| invoiceNew | ({ amountSat, description?, maxTime? }) | PaymentInvoiceDetails \| undefined | Create an invoice to receive funds. |
| invoiceStatus | ({ invoiceId, type }) | IncomingPayment \| OutgoingPayment \| undefined | Look up a payment by hash. |
| fundsWithdraw | ({ amountSat, address, feeRateSatByte?, maxTime? }) | SentPayment \| undefined | Send to a Lightning address or on-chain address (auto-detected). |
| fundsData | <T>({ type, params?, maxTime? }) | T \| undefined | Query any Phoenixd REST endpoint and cache the result. |
| fundsBalance | () | LNBalance \| undefined | On-chain and Lightning balance summary. |
| fundsIncoming | (count?) | IncomingPayment[] \| undefined | Recent incoming payments (default 30). |
| fundsOutgoing | (count?) | OutgoingPayment[] \| undefined | Recent outgoing payments (default 30). |
| zapProcess | ({ lnAddress, amountMsat, nostrEvent }) | ZapRequestResponse \| undefined | Create a Zap invoice and publish its receipt once paid. |
invoiceNewvalidatesamountSat, and if Phoenixd fails to issue the invoice it restarts the node and retries once.
Invoices
// Create an invoice to receive funds
const invoice = client.invoiceNew({ amountSat: 1000, description: "test" });
// → { invoiceString: "lnbc1...", invoiceId: "abc..." }
// Check if it has been paid
import { PaymentDirection } from "@coinexams/lightning";
const payment = client.invoiceStatus({
invoiceId: "abc...",
type: PaymentDirection.Incoming,
});
// → IncomingPayment | OutgoingPayment | undefinedPayments
// Lightning address (auto-detected by address format)
const sent = client.fundsWithdraw({
amountSat: 500,
address: "[email protected]",
});
// → { recipientAmountSat: 500, paymentHash: "...", ... }
// On-chain address (auto-detected by address format)
const onchain = client.fundsWithdraw({
amountSat: 10000,
address: "bc1...",
});
// Full balance details
const balance = client.fundsBalance();
// → { balanceSat: 50000, feeCreditSat: 100 }
// Recent incoming and outgoing payments
const incoming = client.fundsIncoming(10);
const outgoing = client.fundsOutgoing(10);
// Custom data query
const data = client.fundsData({
type: "payments/incoming",
params: { limit: 10 },
});Nostr Zaps
zapProcess ties the whole flow together: it requests an invoice for the given amountMsat, signs a Zap request with the configured server key, and polls until the invoice is paid before publishing a kind-9735 receipt to the sender's relays plus defaultRelays.
const result = client.zapProcess({
lnAddress: `[email protected]`,
amountMsat: 100_000,
nostrEvent: `<zap-request-json>`,
});
if (!result) throw new Error("zap failed");
// result.invoice.pr → "lnbc..."
// result.invoiceId → "..." use with invoiceStatus to confirm settlementError handling
- Every method returns
undefinedon failure instead of throwing. - Failures are logged as
console.error(<ISO timestamp>, "<method> failed", <error>). - Check the return value at the call site to detect a failure.
CLI setup
The package ships a celightning binary that installs, configures, and starts Phoenixd as a systemd service:
npx @coinexams/lightning setup
npx @coinexams/lightning setup --seed "<12-or-24-word phrase>" --port 9740
npx @coinexams/lightning --version
npx @coinexams/lightning --helpRequires Linux with systemd, sudo, and the ability to install wget, unzip, curl, and jq.
FAQ
What is @coinexams/lightning?
A Node.js SDK that wraps a local Phoenixd node, exposing invoices, payments, balances, LNURL-pay, and Nostr Zap receipts through one typed client.
Is it free? Yes. It is open source under the Apache-2.0 license. You pay only Bitcoin network/Lightning routing fees to your own node.
Does it work in the browser?
No. It is a Node-only package: it shells out via node:child_process and uses Buffer, and it needs a local Phoenixd node on localhost.
What are the dependencies?
bech32 for Nostr key encoding and nostr-tools for event signing, verification, and relay publishing.
Is it TypeScript?
Yes. It is written in TypeScript and ships complete declarations at dist/index.d.ts.
Which frameworks does it support?
Any Node.js >=18 runtime — Express, Fastify, NestJS, serverless handlers, or plain scripts.
Do I need to run Phoenixd separately?
Yes. npx @coinexams/lightning setup installs and starts it for you, or you can point the SDK at an existing local Phoenixd instance.
Keywords
Bitcoin, Lightning Network, Phoenixd, self-custodial, Lightning invoice, LNURL-pay, Nostr, Nostr Zap, kind-9735, zap receipt, bech32, npub, nsec, Node.js SDK, TypeScript SDK, on-chain payment, payment SDK.
License
Apache-2.0 © CoinExams
