# Lightning Web SDK
An npm package that provides useful and common tools and helpers to build lightning web applications.
Before you start coding, look at example scenarios in our **[Developer Sandbox](https://sandbox.albylabs.com/)**
## π€ π β‘ For Developers using Agents / LLMs / Vibe Coding
Skip the rest of this README and use the [Alby Bitcoin Builder Skill](https://github.com/getAlby/builder-skill) to build a bitcoin app or the [Alby Bitcoin Payment Skill](https://github.com/getAlby/payments-skill) to give your agent payment capabilities. It will handle the rest!
## Manual Installation
```
npm install @getalby/lightning-tools
```
or
```
yarn add @getalby/lightning-tools
```
or for use without any build tools:
```html
```
**This library relies on a global `fetch()` function which will work in [browsers](https://caniuse.com/?search=fetch) and node v18 or newer.** (In older versions you have to use a polyfill.)
## π€ Usage
### Lightning Address
The `LightningAddress` class provides helpers to work with lightning addresses
```js
import { LightningAddress } from "@getalby/lightning-tools/lnurl";
const ln = new LightningAddress("hello@getalby.com");
// fetch the LNURL data
await ln.fetch();
// get the LNURL-pay data:
console.log(ln.lnurlpData); // returns a [LNURLPayResponse](https://github.com/getAlby/js-lightning-tools/blob/master/src/types.ts#L1-L15)
// get the keysend data:
console.log(ln.keysendData);
```
#### Get an invoice:
```js
import { LightningAddress } from "@getalby/lightning-tools/lnurl";
const ln = new LightningAddress("hello@getalby.com");
await ln.fetch();
// request an invoice for 1000 satoshis
// this returns a new `Invoice` class that can also be used to validate the payment
const invoice = await ln.requestInvoice({ satoshi: 1000 });
console.log(invoice.paymentRequest); // print the payment request
console.log(invoice.paymentHash); // print the payment hash
```
#### Verify a payment
```js
import { LightningAddress } from "@getalby/lightning-tools/lnurl";
const ln = new LightningAddress("hello@getalby.com");
await ln.fetch();
const invoice = await ln.requestInvoice({ satoshi: 1000 });
// if the LNURL providers supports LNURL-verify:
const paid = await invoice.verifyPayment(); // returns true of false
if (paid) {
console.log(invoice.preimage);
}
// if you have the preimage for example in a WebLN context
await window.webln.enable();
const response = await window.webln.sendPayment(invoice.paymentRequest);
const paid = invoice.validatePreimage(response.preimage); // returns true or false
if (paid) {
console.log("paid");
}
// or use the convenient method:
await invoice.isPaid();
```
It is also possible to manually initialize the `Invoice`
```js
import { Invoice } from "@getalby/lightning-tools/bolt11";
const invoice = new Invoice({ pr: pr, preimage: preimage });
await invoice.isPaid();
```
#### Boost a LN address:
You can also attach additional metadata information like app name, version, name of the podcast which is boosted etc. to the keysend payment.
```js
import { LightningAddress } from "@getalby/lightning-tools/lnurl";
const ln = new LightningAddress("hello@getalby.com");
await ln.fetch();
const boost = {
action: "boost",
value_msat: 21000,
value_msat_total: 21000,
app_name: "Podcastr",
app_version: "v2.1",
feedId: "21",
podcast: "random podcast",
episode: "1",
ts: 2121,
name: "Satoshi",
sender_name: "Alby",
};
await ln.boost(boost);
```
#### Zapping a LN address on Nostr:
Nostr is a simple, open protocol that enables truly censorship-resistant and global value-for-value publishing on the web. Nostr integrates deeply with Lightning. [more info](https://nostr.how/)
This librarys provides helpers to create [zaps](https://github.com/nostr-protocol/nips/blob/master/57.md).
```js
import { LightningAddress } from "@getalby/lightning-tools/lnurl";
const ln = new LightningAddress("hello@getalby.com");
await ln.fetch();
const response = await ln.zap({
satoshi: 1000,
comment: "Awesome post",
relays: ["wss://relay.damus.io"],
e: "44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
});
console.log(response.preimage); // print the preimage
```
For a full example see [examples/zaps](examples/zaps.js)
#### Zapping a LN address on Nostr using Nostr Wallet Connect:
Native zaps without a browser extension are possible by using a Nostr Wallet Connect WebLN provider.
See [examples/zaps-nwc](examples/zaps-nwc.js)
> All examples in the [examples/](examples/) directory are runnable. See [examples/README.md](examples/README.md) for setup instructions.
### HTTP 402 - requesting HTTP resources that require a payment
L402, X402, MPP are protocol standards based on the HTTP 402 `Payment Required` code
for machine-to-machine payments. It is used to charge for HTTP API requests, tool calls, or agentic payments.
This library includes functions to consome those resources.
#### fetch402(url: string, fetchArgs, options)
`fetch402` is a single function that transparently handles L402 and X402 and MPP protected resources. Use it when you don't know or don't care which protocol the server uses β it will detect the protocol from the response headers and pay accordingly.
- url: the protected URL
- fetchArgs: arguments are passed to the underlying `fetch()` function used to do the HTTP request
- options:
- wallet: any object that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay L402, X402 and MPP invoices.
- credentials (optional): a credential from a previous paid request (`response.payment.credentials`). When provided it is applied to the request before it is sent so the server can authorize it without a new payment β see [Payment info & polling](#payment-info--polling).
##### Examples
```js
import { fetch402 } from "@getalby/lightning-tools/402";
import { NWCClient } from "@getalby/sdk";
const nwc = new NWCClient({
nostrWalletConnectUrl: "nostr+walletconnect://...",
});
await fetch402("https://example.com/protected-resource", {}, { wallet: nwc })
.then((res) => res.json())
.then(console.log)
.finally(() => nwc.close());
```
#### Payment info & polling
All of the 402 fetch helpers (`fetch402`, `fetchWithL402`, `fetchWithX402`, `fetchWithMpp`) return a standard `fetch` `Response`. When a payment was made (or a supplied credential was reused) the response also carries a `payment` property:
```ts
interface PaymentInfo {
paid: boolean; // whether a lightning payment was made for this request
amountSat: number; // amount of the paid invoice, in satoshis (0 when paid is false)
feesPaidMsat?: number; // routing fees in millisatoshis, when reported by the wallet
preimage?: string; // payment preimage, when a payment was made
credentials: {
// reusable credential β pass back via options.credentials
header: string; // e.g. "Authorization" (L402/MPP) or "payment-signature" (x402)
value: string;
};
}
```
This lets you inspect what a request cost, and β by passing `credentials` back on a follow-up request β authorize subsequent calls without paying again (e.g. polling a long-running video/song generation job).
> **Important:** when you pass `credentials`, the helper reuses them and **never pays a second time**. If the server still responds with a `402` (e.g. the credential expired or the balance is depleted), that `402` response is returned to you as-is β the library will not silently pay another invoice. You decide what to do next: retry the same credential, or make a fresh unauthenticated request to pay again.
```js
// First request (no credentials): pays once and returns the content plus a reusable credential
const res = await fetch402(url, { method: "POST", body }, { wallet: nwc });
const job = await res.json();
console.info(`Paid ${res.payment.amountSat} sats`);
// Follow-up requests reuse the credential β these NEVER pay again
const pollRes = await fetch402(
`${url}/status/${job.id}`,
{},
{ wallet: nwc, credentials: res.payment.credentials },
);
if (pollRes.status === 402) {
// credential not (yet) accepted β retry the same credential later, do not re-pay
} else {
console.info(await pollRes.json());
}
```
##### Recovering from a failure after paying
If the invoice is paid but the flow then fails (the wallet times out, or the
request _after_ payment hits a network error), the helper throws a
`Fetch402InterruptedError` instead of a bare `Error`. It carries everything needed
to reconcile the payment **without paying the same invoice again**:
```ts
class Fetch402InterruptedError extends Error {
invoice: string; // the invoice that was paid (or attempted)
paymentHash: string; // decoded from the invoice β use it to look up settlement
amountSat: number; // invoice amount in satoshis, decoded from the invoice
paid: boolean; // whether the wallet reported the payment succeeded
preimage?: string; // present when paid
feesPaidMsat?: number; // routing fees in millisatoshis, present when paid (if reported)
credentials?: PaymentCredentials; // present when paid β retry with these
pendingPayment: PendingPayment; // opaque token to resume via options.resume
cause?: unknown; // the underlying wallet/fetch error
}
```
Every field is plain data, so the error survives `JSON.stringify` and can be
forwarded across process/CLI boundaries. (After a round-trip it's a plain
object, so match on `e.name === "Fetch402InterruptedError"` rather than
`instanceof`.)
```js
try {
const res = await fetch402(url, { method: "POST", body }, { wallet: nwc });
} catch (e) {
if (e.name !== "Fetch402InterruptedError") throw e;
if (e.paid) {
// Payment succeeded but the follow-up request failed. The credential is
// already built β retry with it, DON'T pay again.
await fetch402(
url,
{ method: "POST", body },
{ wallet: nwc, credentials: e.credentials },
);
} else {
// payInvoice never returned (e.g. a timeout), but the payment may have
// settled anyway. Ask the wallet whether this payment hash settled.
const lookup = await nwc.lookupInvoice({ payment_hash: e.paymentHash });
if (lookup?.preimage) {
// It settled β you have ALREADY PAID. Resume the same request: pass the
// recovered preimage back with the error's pendingPayment and the library
// rebuilds the credential internally and sends it WITHOUT paying again.
await fetch402(
url,
{ method: "POST", body },
{
wallet: nwc,
resume: {
pendingPayment: e.pendingPayment,
preimage: lookup.preimage,
},
},
);
} else if (lookup?.state === "failed") {
// Explicitly FAILED β no funds moved, safe to retry from scratch.
await fetch402(url, { method: "POST", body }, { wallet: nwc });
} else {
// Still pending / in-flight β do NOT retry yet: it may still settle and a
// fresh payment would double-pay. Wait and re-check the payment hash.
}
}
}
```
#### L402
L402 is a protocol standard based on the HTTP 402 Payment Required error code
designed to support the use case of charging for services and
authenticating users in distributed networks.
This library includes a `fetchWithL402` function to consume L402 protected resources.
##### fetchWithL402(url: string, fetchArgs, options)
- url: the L402 protected URL
- fetchArgs: arguments are passed to the underlying `fetch()` function used to do the HTTP request
- options:
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay the L402 invoice.
- credentials (optional): a credential from a previous paid request β see [Payment info & polling](#payment-info--polling).
##### Examples
```js
import { fetchWithL402 } from "@getalby/lightning-tools/402/l402";
import { NWCClient } from "@getalby/sdk";
const nwc = new NWCClient({
nostrWalletConnectUrl: "nostr+walletconnect://...",
});
await fetchWithL402(
"https://l402.example.com/protected-resource",
{},
{ wallet: nwc },
)
.then((res) => res.json())
.then(console.log)
.finally(() => nwc.close());
```
#### X402
Similar to L402 X402 is an open protocol for machine-to-machine payments built on the HTTP 402 Payment Required status code.
It enables APIs and resources to request payments inline, without prior registration or authentication.
This library includes a `fetchWithX402` function to consume X402-protected resources that support the lightning network.
(Note: X402 works also with other coins and network. This library supports X402 resources that accept Bitcoin on the lightning network)
##### fetchWithX402(url: string, fetchArgs, options)
- url: the X402 protected URL
- fetchArgs: arguments are passed to the underlying `fetch()` function used to do the HTTP request
- options:
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay the X402 invoice.
- credentials (optional): a credential from a previous paid request β see [Payment info & polling](#payment-info--polling).
##### Examples
```js
import { fetchWithX402 } from "@getalby/lightning-tools/402/x402";
import { NWCClient } from "@getalby/sdk";
const nwc = new NWCClient({
nostrWalletConnectUrl: "nostr+walletconnect://...",
});
await fetchWithX402(
"https://x402.example.com/protected-resource",
{},
{ wallet: nwc },
)
.then((res) => res.json())
.then(console.log)
.finally(() => nwc.close());
```
#### MPP
MPP is an open protocol for machine-to-machine payments built on the HTTP 402 Payment Required status code.
Charge for API requests, tool calls, or contentβagents and apps pay per request in the same HTTP call.
This library includes a `fetchWithMpp` function to consume MPP-protected resources that support the lightning network.
(Note: MPP works also with other payment methods. This library supports resources that accept Bitcoin on the lightning network)
##### fetchWithMpp(url: string, fetchArgs, options)
- url: the MPP protected URL
- fetchArgs: arguments are passed to the underlying `fetch()` function used to do the HTTP request
- options:
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay the MPP invoice.
- credentials (optional): a credential from a previous paid request β see [Payment info & polling](#payment-info--polling).
##### Examples
```js
import { fetchWithMpp } from "@getalby/lightning-tools/402/mpp";
import { NWCClient } from "@getalby/sdk";
const nwc = new NWCClient({
nostrWalletConnectUrl: "nostr+walletconnect://...",
});
await fetchWithMpp(
"https://mpp.example.com/protected-resource",
{},
{ wallet: nwc },
)
.then((res) => res.json())
.then(console.log)
.finally(() => nwc.close());
```
### Basic invoice decoding
You can initialize an `Invoice` to decode a payment request.
```js
import { Invoice } from "@getalby/lightning-tools/bolt11";
const invoice = new Invoice({ pr });
const { paymentHash, satoshi, description, createdDate, expiryDate } = invoice;
```
> If you need more details about the invoice, use a dedicated BOLT11 decoding library.
### π΅ Fiat conversions
Helpers to convert sats values to fiat and fiat values to sats.
##### getFiatCurrencies(): Promise
Returns the list of available fiat currencies sorted by priority
##### getFiatValue(satoshi: number, currency: string): number
Returns the fiat value for a specified currency of a satoshi amount
##### getSatoshiValue(amount: number, currency: string): number
Returns the satoshi value for a specified amount (in the smallest denomination) and currency
##### getFormattedFiatValue(satoshi: number, currency: string, locale: string): string
Like `getFiatValue` but returns a formatted string for a given locale using JavaScript's `toLocaleString`
#### Examples
```js
await fiat.getFiatCurrencies();
await fiat.getFiatValue({ satoshi: 2100, currency: "eur" });
await fiat.getSatoshiValue({ amount: 100, currency: "eur" }); // for 1 EUR
await fiat.getFormattedFiatValue({
satoshi: 2100,
currency: "usd",
locale: "en",
});
```
### BIP21 (`bitcoin:` URIs)
Parse [BIP21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki) payment URIs, including the unified-QR `lightning=` fallback parameter.
```js
import { parseBip21 } from "@getalby/lightning-tools";
const result = parseBip21(
"bitcoin:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=0.001&label=Donation&lightning=lnbc...",
);
if (result) {
result.address; // "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
result.amount; // 0.001 (BTC)
result.amountSats; // 100000
result.label; // "Donation"
result.lightning; // BOLT11 fallback, if present
result.lno; // BOLT12 offer, if present
result.unknownRequiredParams; // reject the URI if non-empty (BIP21 `req-*` rule)
}
```
`parseBip21` returns `null` for inputs that don't start with the `bitcoin:` scheme. Address validation is intentionally out of scope β validate the returned `address` with your own check if needed.
### π€ Lightning Address Proxy
This library uses a [proxy](https://github.com/getAlby/lightning-address-details-proxy) to simplify requests to lightning providers.
- Many ln addresses don't support CORS, which means fetching the data directly in a browser environment will not always work.
- Two requests are required to retrieve lnurlp and keysend data for a lightning address. The proxy will do these for you with a single request.
You can disable the proxy by explicitly setting the proxy to false when initializing a lightning address:
```js
const lightningAddress = new LightningAddress("hello@getalby.com", {
proxy: false,
});
```
## crypto dependency
If you get an `crypto is not defined` in NodeJS error you have to import it first:
```js
import * as crypto from 'crypto'; // or 'node:crypto'
globalThis.crypto = crypto as any;
//or: global.crypto = require('crypto');
```
## fetch() dependency
This library relies on a global fetch object which will work in browsers and node v18.x or newer. In old version you can manually install a global fetch option or polyfill if needed.
For example:
```js
import fetch from "cross-fetch"; // or "@inrupt/universal-fetch"
globalThis.fetch = fetch;
// or as a polyfill:
import "cross-fetch/polyfill";
```
## π Development
```
yarn install
yarn run build
```
## Need help?
We are happy to help, please contact us or create an issue.
- [Twitter: @getAlby](https://twitter.com/getAlby)
- [Telegram group](https://t.me/getAlby)
- support at getalby.com
- [bitcoin.design](https://bitcoin.design/) Discord community (find us on the #alby channel)
- Read the [Alby developer guide](https://guides.getalby.com/overall-guide/alby-for-developers/getting-started) to better understand how Alby packages and APIs can be used to power your app.
## License
MIT