Menu
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

AspectValue
StatusDraft
LayerApplication
PurposeReusable payment requests
DependenciesBOLT-01, BOLT-04

Note: BOLT-12 is still under development. Implementations may vary.

Why BOLT-12?

BOLT-11 Limitations

ProblemBOLT-11BOLT-12
ReusabilitySingle useReusable
Payer privacySender revealedBlinded paths
Static amountsFixedNegotiable
SubscriptionsNot supportedNative support
Proof of paymentPayment hashPayer 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:

TypeFieldDescription
2chainsSupported chains
6currencyISO 4217 currency code
8amountAmount in currency units
10descriptionHuman-readable description
12featuresRequired/supported features
16pathsBlinded paths to reach payee
20issuerIssuer name
22quantity_minMinimum quantity
24quantity_maxMaximum quantity
26node_idDestination node
240signatureSchnorr 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

FieldDescription
time_unitseconds, days, weeks, months, years
periodNumber of units
startUnix timestamp of first payment
limitMaximum number of payments
paywindowAcceptable 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

ImplementationStatus
Core LightningSupported
LNDExperimental
EclairIn development
LDKIn 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'])
  • BOLT-04 - Onion routing for messages
  • BOLT-11 - Current invoice format

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"
  }
}