BOLT-bolt-12 Draft
BOLT-12: Offers
Lightning Offers protocol (draft). Reusable payment requests, blinded paths, and recurring payments.
| Type | Basis of Lightning Technology |
| Number | bolt-12 |
| Status | Draft |
| Authors | Lightning Network Developers |
| Original | https://github.com/lightning/bolts/blob/master/12-offer-encoding.md |
| Requires |
⚠️ This specification is still a draft and may change.
BOLT-12: Offers (Draft)
BOLT-12 defines a new payment request format that improves on BOLT-11 invoices. Offers are reusable, support recurring payments, and provide better privacy through blinded paths.
Specification Summary
| Aspect | Value |
|---|---|
| Status | Draft |
| Layer | Application |
| Purpose | Reusable payment requests |
| Dependencies | BOLT-01, BOLT-04 |
Note: BOLT-12 is still under development. Implementations may vary.
Why BOLT-12?
BOLT-11 Limitations
| Problem | BOLT-11 | BOLT-12 |
|---|---|---|
| Reusability | Single use | Reusable |
| Payer privacy | Sender revealed | Blinded paths |
| Static amounts | Fixed | Negotiable |
| Subscriptions | Not supported | Native support |
| Proof of payment | Payment hash | Payer proof |
Message Types
Offer
A reusable payment endpoint:
lno1qsgq...
│ │
│ └─ Data (TLV encoded)
└─ Prefix: offer
Invoice Request
Request invoice from an offer:
lnr1qsgq...
│ │
│ └─ Data (TLV encoded)
└─ Prefix: invoice_request
Invoice
Response to invoice request:
lni1qsgq...
│ │
│ └─ Data (TLV encoded)
└─ Prefix: invoice
Offer Structure
TLV-encoded fields:
| Type | Field | Description |
|---|---|---|
| 2 | chains | Supported chains |
| 6 | currency | ISO 4217 currency code |
| 8 | amount | Amount in currency units |
| 10 | description | Human-readable description |
| 12 | features | Required/supported features |
| 16 | paths | Blinded paths to reach payee |
| 20 | issuer | Issuer name |
| 22 | quantity_min | Minimum quantity |
| 24 | quantity_max | Maximum quantity |
| 26 | node_id | Destination node |
| 240 | signature | Schnorr signature |
Example Offer
Offer:
description: "Coffee at Alice's"
amount: 5000 msat
issuer: "Alice's Coffee Shop"
paths: [blinded path to Alice]
node_id: 03abc...
Blinded Paths
Offers use blinded paths for privacy:
Payer → Intro Node → ??? → ??? → Payee
│
└─ Only intro node is known
Blinded Path Structure
┌─────────────────────────────────────────┐
│ first_node_id (33 bytes) │
│ first_path_key (33 bytes) │
│ num_hops (1 byte) │
│ blinded_hops[] │
│ ├─ blinded_node_id (33 bytes) │
│ └─ encrypted_recipient_data │
└─────────────────────────────────────────┘
Creating Blinded Path
def create_blinded_path(
hops: list,
session_key: bytes
) -> BlindedPath:
"""Create blinded path for privacy."""
blinded_hops = []
blinding_key = session_key
for hop in hops:
# Derive blinding factor
ss = ecdh(blinding_key, hop.node_id)
# Blind the node ID
blinded_id = blind_node_id(hop.node_id, blinding_key)
# Encrypt recipient data
encrypted = encrypt_recipient_data(
ss,
hop.payload
)
blinded_hops.append(BlindedHop(blinded_id, encrypted))
# Update blinding key
blinding_key = derive_next_blinding(blinding_key, ss)
return BlindedPath(
first_node_id=hops[0].node_id,
first_path_key=session_key,
hops=blinded_hops
)
Payment Flow
1. Fetch Offer
User scans: lno1qsgq...
Wallet decodes offer
2. Create Invoice Request
def create_invoice_request(offer, amount, payer_key):
"""Request invoice from offer."""
request = InvoiceRequest(
offer_id=offer.id,
amount=amount,
payer_key=payer_key, # For proof of payer
payer_info=encrypted_contact # Optional
)
return sign_request(request, payer_key)
3. Send via Onion Message
Payer ──[onion_message]──→ Offer Issuer
(via blinded path)
4. Receive Invoice
Payer ←──[onion_message]── Invoice
(via reply path)
5. Pay Invoice
Standard HTLC payment using invoice details.
Recurring Payments
BOLT-12 supports subscriptions:
offer = Offer(
description="Monthly subscription",
amount=10000, # 10 sats
recurrence={
"time_unit": "month",
"period": 1,
"start": 1706745600 # First payment
}
)
Recurrence Fields
| Field | Description |
|---|---|
time_unit | seconds, days, weeks, months, years |
period | Number of units |
start | Unix timestamp of first payment |
limit | Maximum number of payments |
paywindow | Acceptable payment timing |
Payer Proofs
Unlike BOLT-11, payer can prove they made payment:
# Payer key in invoice_request
payer_key = generate_keypair()
# Sign the invoice request
signature = sign(invoice_request_bytes, payer_key.private)
# Later, can prove payment by showing:
# - invoice_request (with payer_key)
# - signature
# - payment preimage
Implementation Status
| Implementation | Status |
|---|---|
| Core Lightning | Supported |
| LND | Experimental |
| Eclair | In development |
| LDK | In development |
Agent Considerations
When to Use BOLT-12
✅ Use for:
- Reusable payment endpoints
- Subscription services
- Enhanced privacy needs
- Static payment codes
❌ Still use BOLT-11 for:
- Maximum compatibility
- One-time payments
- Legacy wallet support
Creating Offers
# Core Lightning example
offer = cln.offer(
amount="10000msat",
description="Agent service fee"
)
print(f"Offer: {offer['bolt12']}")
Fetching Invoice
# Request invoice from offer
invoice = cln.fetchinvoice(
offer=offer_string,
amount_msat=10000
)
# Pay the invoice
cln.pay(invoice['invoice'])
Related BOLTs
Machine-Readable Summary
{
"bolt": "12",
"title": "Offers",
"status": "draft",
"message_types": [
{"prefix": "lno", "name": "offer"},
{"prefix": "lnr", "name": "invoice_request"},
{"prefix": "lni", "name": "invoice"}
],
"key_features": [
"reusable-payments",
"blinded-paths",
"recurring-payments",
"payer-proofs"
],
"implementation_support": {
"core-lightning": "supported",
"lnd": "experimental"
}
}