Decode, explore, and learn the inner workings of BOLT12 Lightning Network offers.
Paste a BOLT12 offer above to decode it.
Hover over a highlighted section above to see more information.
Edit the code below and see the result instantly. The bolt12 library runs entirely in your browser.
npm install bolt12-utils
BOLT12 is a protocol for requesting and receiving invoices over the Lightning Network itself. It replaces the static, single-use BOLT11 invoice with a reusable offer that a merchant can publish once (as a QR code, link, or NFC tag), from which payers derive fresh invoices on demand.
BOLT12 defines a 4-step protocol between a merchant and a payer:
lnoMerchant publishes a static, reusable offer (QR code, web page, NFC). Contains description, amount, paths, and issuer identity.
lnrPayer decodes the offer and sends an invoice request over an onion message. Includes payer identity, quantity, and reply path.
lniMerchant validates the request, generates a fresh invoice with a unique payment hash, and sends it back over an onion message.
Payer validates the invoice signature, then pays using standard HTLC routing (possibly via the blinded paths in the invoice).
An offer is a TLV stream encoded with the lno prefix. These are the defined fields:
| Type | Field | Description |
|---|---|---|
| 2 | offer_chains | Which blockchain(s) the offer is valid for (default: Bitcoin mainnet) |
| 4 | offer_metadata | Arbitrary bytes for stateless request validation |
| 6 | offer_currency | ISO 4217 currency code (e.g., "USD") if amount is not in msat |
| 8 | offer_amount | Price in millisatoshis or currency units |
| 10 | offer_description | Human-readable description of the offer |
| 12 | offer_features | Feature bits the offer requires/supports |
| 14 | offer_absolute_expiry | Unix timestamp after which the offer expires |
| 16 | offer_paths | Blinded paths to reach the merchant |
| 18 | offer_issuer | Human-readable name of the issuer |
| 20 | offer_quantity_max | Maximum quantity a payer can request |
| 22 | offer_issuer_id | Merchant's compressed public key (33 bytes) |
Blinded paths let a node receive messages and payments without revealing its node ID or network position. A path consists of an introduction node, a blinding point, and encrypted hops that only each successive node can decrypt. They appear in offers (for onion messages) and invoices (for payment routing).
All BOLT12 messages use Type-Length-Value encoding with BigSize integers. Fields must appear in ascending type order. Unknown odd types are ignored (forward compatible); unknown even types cause rejection. This makes the protocol natively extensible.
Signatures use BIP-340 Schnorr over a Merkle root computed from all TLV fields. Each field becomes a leaf paired with a nonce, then branches are built bottom-up. This enables selective disclosure: either party can reveal specific fields to a third party without exposing the entire message.
BOLT12 strings use bech32-style encoding but without the 6-character checksum.
QR codes provide error detection, and cryptographic signatures serve as integrity
checks. The + character can split long strings across lines.
Uppercase is preferred for QR efficiency.
A payer proof (lnp) allows a payer to prove they paid a specific invoice
while selectively disclosing only certain fields. This enables privacy-preserving proof of payment
for receipts, warranties, and dispute resolution.
The payer chooses which invoice fields to reveal (e.g., payment hash, node ID) and which to keep private (e.g., amount, description). Type 0 (invreq_metadata) is always kept secret.
The proof includes the Merkle branch hashes needed to reconstruct the root from the disclosed fields, plus nonce hashes for each included TLV. The verifier can recompute the root and verify the invoice signature.
The payer signs SHA256(note || merkle_root) with their key, binding an optional note (e.g., "Payment for coffee") to the proof.
| Type | Field | Description |
|---|---|---|
| 88 | invreq_payer_id | Payer's public key (required, always included) |
| 168 | invoice_payment_hash | Payment hash from the invoice (required) |
| 176 | invoice_node_id | Merchant's node ID for signature verification (required) |
| 240 | signature | Invoice signature (BIP-340 Schnorr, 64 bytes) |
| 242 | preimage | Payment preimage (32 bytes, proves payment was completed) |
| 244 | omitted_tlvs | Marker numbers for omitted fields (BigSize array) |
| 246 | missing_hashes | Merkle branch hashes for tree reconstruction (32 bytes each) |
| 248 | leaf_hashes | Nonce hashes for included TLVs (32 bytes each) |
| 250 | payer_signature | Payer's BIP-340 signature + optional UTF-8 note |
Further your Lightning Network knowledge with these resources:
The official spec for offer encoding, invoice requests, and invoices.
Read SpecThe community hub for BOLT12 adoption, tooling, and wallet support status.
VisitAll Lightning Network protocol specifications in one place.
ExploreHow Lightning Dev Kit implements BOLT12 offers and invoices.
Learn MoreTechnical overview and history of the offers protocol development.
ReadPure TypeScript BOLT12 decoder. Zero native dependencies. All test vectors passing.
GitHubIf you find this library useful, consider donating via on-chain (Taproot), ARK, or BOLT12:
| Method | Address / Offer |
|---|---|
| On-chain (Taproot) | bc1pyys36jag8qug09c36d9j6427kny3d0x08u3wf5l89sks5sxyq3fsp2vddt |
| ARK | ark1qq4hfssprtcgnjzf8qlw2f78yvjau5kldfugg29k34y7j96q2w4t5fjuejh7a4eauna0vf2eegcw95w3dyjzl8gykpye50e9qneuwezqdfwupf |
| BOLT12 Offer | lno1pgqppmsrse80qf0aara4slvcjxrvu6j2rp5ftmjy4yntlsmsutpkvkt6878sxn8g96fuzlhw75hendmuhjy0gp607tsgzaasdvjmstcwcgc6vgwyqgp6mv9u948ngt3j0urev4ga0vw06cpvasexgn00feez9vfgdkyykfgqxdaa8ysjuy8um26ekywlceecwalj0zvqu5h0dd486uhhzvj9m3qlnmaa9awj0cft7x95h7yn9vaep4gm055q8rsctl6lthka2htmk8pzxvgyzae72gnapuhg2v9rtgwfg4mlr56lqqerat2vv2u2aka8e592vqluf5erqqs2ve30snd2pr2d0h7fdfl9js6wyzjl4c66nu6d32nj4w2ft0um9q4q |
BIP21/321 unified URI:
bitcoin:bc1pyys36jag8qug09c36d9j6427kny3d0x08u3wf5l89sks5sxyq3fsp2vddt?ark=ark1qq4hfssprtcgnjzf8qlw2f78yvjau5kldfugg29k34y7j96q2w4t5fjuejh7a4eauna0vf2eegcw95w3dyjzl8gykpye50e9qneuwezqdfwupf&lno=lno1pgqppmsrse80qf0aara4slvcjxrvu6j2rp5ftmjy4yntlsmsutpkvkt6878sxn8g96fuzlhw75hendmuhjy0gp607tsgzaasdvjmstcwcgc6vgwyqgp6mv9u948ngt3j0urev4ga0vw06cpvasexgn00feez9vfgdkyykfgqxdaa8ysjuy8um26ekywlceecwalj0zvqu5h0dd486uhhzvj9m3qlnmaa9awj0cft7x95h7yn9vaep4gm055q8rsctl6lthka2htmk8pzxvgyzae72gnapuhg2v9rtgwfg4mlr56lqqerat2vv2u2aka8e592vqluf5erqqs2ve30snd2pr2d0h7fdfl9js6wyzjl4c66nu6d32nj4w2ft0um9q4q