Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save vincenzopalazzo/8a14f9651a04a0edf842272f86343185 to your computer and use it in GitHub Desktop.

Select an option

Save vincenzopalazzo/8a14f9651a04a0edf842272f86343185 to your computer and use it in GitHub Desktop.
Split Payments via BOLT 12
# bLIP-XXXX: Split Payments via BOLT 12
```
bLIP: XXXX
Title: Split Payments via BOLT 12 Offers
Status: Draft
Author: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Created: 2026-04-11
License: CC0
```
## Abstract
This bLIP defines a protocol for splitting a payment among multiple
participants using BOLT 12 offers. The payer who settles the full
invoice with a merchant can request reimbursement from one or more
friends by issuing per-participant offers that carry structured
split-context metadata in the experimental TLV range.
Wallets that understand the split TLV fields can present a rich
"split request" experience; wallets that do not simply see a normal
offer for the requested amount.
## Motivation
Splitting a bill is one of the most common payment interactions
between friends, yet Lightning has no standardized mechanism for it.
Users currently resort to out-of-band coordination — mental math,
chat messages with amounts, and manually created invoices — all of
which are error-prone and provide no auditability.
BOLT 12 offers are the natural primitive for this: they are reusable,
they carry descriptive metadata, and they support onion-message-based
invoice negotiation without requiring the participants to share
node identifiers out of band. By defining a small set of
experimental TLV fields, wallets can automate the entire split
workflow while remaining fully backward compatible with existing
BOLT 12 implementations.
## Protocol Flow
The protocol involves three roles:
- **Payer** (Alice): pays the merchant in full and initiates the split.
- **Participant** (Bob, and possibly others): owes Alice a share.
- **Merchant**: receives the original payment (standard BOLT 12 flow, unchanged).
### Step 1 — Alice Pays the Merchant
Alice pays the merchant through the normal BOLT 12 user-pays-merchant
flow (offer → invoice_request → invoice → payment). She obtains a
settled invoice containing `invoice_payment_hash` and
`invoice_amount`.
### Step 2 — Alice Creates a Split Offer
For each participant, Alice's wallet creates an offer:
- `offer_amount` is set to the participant's share (in msat).
- `offer_description` is a human-readable summary,
e.g. `"Split: dinner at Ristorante Da Mario"`.
- `offer_issuer` identifies Alice (e.g. `alice@example.com`).
- `offer_issuer_id` or `offer_paths` are set as usual so the
participant can reach Alice's node.
- `offer_absolute_expiry` SHOULD be set to a reasonable window
(e.g. 7 days).
In addition, Alice's wallet sets the following experimental TLV
fields in the offer (see [Split TLV Fields](#split-tlv-fields)):
| Type | Name | Purpose |
|---------------|---------------------------------|---------|
| 1000000001 | `split_original_payment_hash` | Links to the settled merchant invoice |
| 1000000003 | `split_total_amount` | Total amount paid to the merchant |
| 1000000005 | `split_num_participants` | Number of people in the split (including Alice) |
| 1000000007 | `split_merchant_description` | The merchant's `offer_description` or `offer_issuer` from the original offer |
### Step 3 — Alice Sends the Offer to the Participant
Alice transmits the offer to Bob. This can happen via:
1. **QR code or NFC** — if they are physically co-located.
2. **Onion message** — if Alice knows a route to Bob's node
(e.g. via a shared channel or Bob's `node_id`).
3. **Out-of-band** — copy-paste the `lno1...` string in a chat
message, email, etc.
### Step 4 — Bob Pays
Bob's wallet decodes the offer. If it recognizes the `split_*`
TLV fields, it presents a dedicated split-request UI showing
the original merchant, total amount, number of participants, and
Bob's share. If it does not recognize them (they are odd-typed
and therefore ignorable), it shows a normal offer for the requested
amount — the payment still works.
Bob sends an `invoice_request` to Alice via the standard BOLT 12
flow. Alice's node returns an `invoice`. Bob pays the invoice.
### Step 5 — Alice Receives Reimbursement
Alice's wallet matches the incoming payment to the split context
(via `split_original_payment_hash`) and marks Bob's share as
settled.
```
┌────────┐ ┌────────┐ ┌──────────┐
│ Alice │ │ Bob │ │ Merchant │
└───┬────┘ └───┬────┘ └────┬─────┘
│ offer (scan/browse) │
│──────────────────────────────────────>│ (1) normal
│ invoice_request │ BOLT 12
│<─────────────────────────────────────│ flow
│ invoice │
│──────────────────────────────────────>│
│ payment │
│──────────────────────────────────────>│
│ │
│ split offer (QR / onion / chat) │
│─────────────────>│ │
│ invoice_request │ │ (2) split
│<─────────────────│ │ reimbursement
│ invoice │ │ flow
│─────────────────>│ │
│ payment │ │
│<─────────────────│ │
│ │ │
```
## Split TLV Fields
All fields use the experimental offer TLV range
(1000000000–1999999999) as permitted by BOLT 12. All types are
**odd**, making them optional for readers that do not understand them.
### `split_original_payment_hash`
1. type: 1000000001 (`split_original_payment_hash`)
2. data:
* [`sha256`:`payment_hash`]
The `payment_hash` from the settled merchant invoice
(`invoice_payment_hash`). This allows the participant's wallet to
verify (if it witnessed the original payment) and allows Alice's
wallet to correlate reimbursements.
### `split_total_amount`
1. type: 1000000003 (`split_total_amount`)
2. data:
* [`tu64`:`total_msat`]
The total amount Alice paid to the merchant, in millisatoshis.
Combined with `offer_amount` (the participant's share), the wallet
can display the split ratio to the user.
### `split_num_participants`
1. type: 1000000005 (`split_num_participants`)
2. data:
* [`tu64`:`count`]
The total number of participants in the split, **including** the
payer (Alice). A value of 2 means "just you and me". Wallets
SHOULD display this so Bob knows the split is fair.
### `split_merchant_description`
1. type: 1000000007 (`split_merchant_description`)
2. data:
* [`...*utf8`:`description`]
A copy of the merchant's `offer_description` or `offer_issuer`
from the original offer. This gives the participant context about
*what* was paid for, without requiring them to have seen the
original offer.
## Requirements
### The Payer (Split Initiator)
A wallet that initiates a split:
- MUST have successfully settled the merchant invoice before
creating split offers.
- MUST set `offer_amount` to the participant's share in
millisatoshis.
- MUST set `offer_description` to include a human-readable
indication that this is a split request.
- MUST set `split_original_payment_hash` to the `payment_hash`
of the settled merchant invoice.
- MUST set `split_total_amount` to the `invoice_amount` from the
settled merchant invoice.
- MUST set `split_num_participants` to the total number of people
sharing the payment (including the payer).
- SHOULD set `split_merchant_description` to the merchant's
`offer_description` or `offer_issuer`.
- SHOULD set `offer_absolute_expiry` to a reasonable deadline.
- SHOULD track the settlement status of each participant's
reimbursement.
### The Participant (Split Responder)
A wallet that receives a split offer:
- if it recognizes the `split_*` TLV fields:
- SHOULD display the split context (merchant, total, number of
participants, and the user's share) before confirming payment.
- MAY reject the split if the share amount seems unreasonable
relative to the total (e.g. share > total).
- if it does not recognize the `split_*` TLV fields:
- MUST treat it as a normal offer (per the odd-type-is-ok rule).
- MUST follow the standard BOLT 12 invoice_request flow to pay.
## Backward Compatibility
This proposal is fully backward compatible. All new TLV fields are
in the BOLT 12 experimental range and use odd type numbers, so
existing implementations will ignore them. The split offer is a
valid BOLT 12 offer in all respects; the `split_*` fields are purely
additive UX hints.
A participant using a wallet without split support will simply see
an offer from Alice for a given amount, with a description indicating
it is a split. The payment flow is identical.
## Rationale
**Why offers and not invoice_requests?**
The "user-pays-merchant" offer flow is the right direction here:
Alice is requesting money *from* Bob. She publishes an offer (she is
the "merchant" in BOLT 12 terms), and Bob requests an invoice and
pays. This reuses the most well-supported BOLT 12 flow and requires
no new message types.
**Why not use `offer_metadata` for the split context?**
`offer_metadata` is opaque and implementation-specific. Defining
named TLV fields in the experimental range is more interoperable:
different wallet implementations can independently parse and display
the split context without needing to agree on an `offer_metadata`
encoding.
**Why include the original `payment_hash`?**
It serves two purposes: (1) Alice's wallet can correlate incoming
reimbursements to the original purchase, and (2) if Bob also has
visibility of the original payment (e.g. they were both present when
the merchant showed the QR code), his wallet can verify the claim.
**Why odd TLV types?**
Odd types are "it's OK to be odd" — readers that don't understand
them simply skip them. This ensures the split offer degrades
gracefully to a plain offer on non-supporting wallets.
**Why not a single multi-party payment to the merchant?**
Coordinating multiple payers to a single invoice introduces
complexity (partial payments, timeouts, atomicity). The
pay-then-reimburse model is simpler, works today with no merchant
cooperation, and matches user expectations from traditional
payment apps.
## Open Questions
1. **Privacy of the original payment_hash**: Including the merchant's
`payment_hash` leaks information about Alice's payment to the
participant. Is this acceptable, or should it be optional /
blinded?
2. **Unequal splits**: The current design supports arbitrary per-participant
amounts (each offer has its own `offer_amount`). Should there be
a TLV field for the split *ratio* or *index* to help wallets
present a unified view?
3. **Group coordination**: For splits among > 2 people, should there be
a `split_group_id` (random identifier) so wallets that receive
multiple split offers from Alice can group them in the UI?
4. **Onion message delivery**: Sending split offers via onion messages
is attractive for privacy, but requires Alice to know a route to
each participant. Should this bLIP recommend a specific delivery
mechanism, or remain transport-agnostic?
## Reference Implementation
*TODO: link to reference implementation once available.*
## Copyright
This bLIP is licensed under CC0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment