When a routing node is asked to relay payments but the destination is lacking inbound liquidity, the routing node usually simply fails that payment. That is a missed opportunity to earn fees, and a poor user experience. We propose a protocol to add more liquidity on-the-fly to be able to route those payments and earn additional fees for the liquidity provided.
This is particularly useful when combined with zero-conf (to avoid locking up HTLCs while the additional liquidity is waiting for confirmations) for mobile wallet users. But this isn't limited to those use-cases, and can make sense between routing nodes as well.
Bits | Name | Description | Context | Dependencies |
---|---|---|---|---|
260/261 | on_the_fly_funding |
On-the-fly channel funding | IN |
Nodes may activate this feature bit when they want to add more liquidity to lightning channels when incoming payments require it.
-
type: 33000 (
propose_funding_htlc
) -
data:
- [
chain_hash
:chain_hash
] - [
32*byte
:proposed_htlc_id
] - [
u64
:amount_msat
] - [
sha256
:payment_hash
] - [
u32
:cltv_expiry
] - [
1366*byte
:onion_routing_packet
]
- [
-
tlv_stream
:propose_funding_htlc_tlvs
-
types:
- type: 0 (
funding_rates
) - data:
- [
u32
:fee_base_sat
] - [
u16
:fee_proportional_basis
]
- [
- type: 0 (
The sending node:
- If it wants to forward an HTLC, but doesn't have a channel with enough funds
for it, and the receiving node supports
on_the_fly_funding
:- MAY send
propose_funding_htlc
if it is able to fund a channel for the corresponding amount. - MUST generate a unique
proposed_htlc_id
. - If this is a forwarded payment from a downstream channel:
- MAY deterministically derive
proposed_htlc_id
from the downstreamchannel_id
andhtlc_id
.
- MAY deterministically derive
- MUST include
amount_msat
,payment_hash
,cltv_expiry
andonion_routing_packet
as if it were sendingupdate_add_htlc
. - MUST include
funding_rates
for the funding fees it will collect after funding the channel. - SHOULD include on-chain fees for the funding transaction in
fee_base_sat
. - If a disconnection happens before receiving
fail_funding_htlc
oraccept_funding_htlcs
:- MUST forget this
propose_funding_htlc
.
- MUST forget this
- MAY send
- Otherwise:
- MUST NOT send
propose_funding_htlc
.
- MUST NOT send
The receiving node:
- If it finds the
funding_rates
acceptable:- MUST decrypt
onion_routing_packet
and handle it as if it came from anupdate_add_htlc
message. - If it would have failed that
update_add_htlc
:- MUST send
fail_funding_htlc
.
- MUST send
- If it would have fulfilled that
update_add_htlc
:- MUST send
accept_funding_htlcs
.
- MUST send
- If a disconnection happens before the corresponding channel funding starts:
- MUST forget this
propose_funding_htlc
.
- MUST forget this
- MUST decrypt
- Otherwise:
- MUST send
fail_funding_htlc
.
- MUST send
When a node is asked to relay a payment on a channel where there isn't enough liquidity, it may provide on-the-fly liquidity on that channel to be able to relay that payment and collect the corresponding routing fees. Recipients are then able to reliably receive payments without actively managing their inbound liquidity.
propose_funding_htlc
is very similar to update_add_htlc
and must be handled
similarly: the recipient must decrypt the onion and aggregate payments (when
they are using multiple parts) before deciding whether to accept or reject a
funding proposal.
The sender gives its current funding_rates
to the recipient, which covers the
on-chain fees for the corresponding funding transaction and the additional
liquidity that is provided.
In most cases, the sender is relaying HTLCs from downstream channels. It must settle those HTLCs to avoid locking liquidity in the network for too long. It thus provides an explicit, absolute expiry to the recipient before which they must decide whether to accept or reject the funding proposal.
If nodes are disconnected, they should forget all pending funding proposals. This removes the need for explicit state tracking and reconnection handling. The funding proposal can be restarted from scratch on reconnection.
- type: 33001 (
fail_funding_htlc
) - data:
- [
32*byte
:proposed_htlc_id
] - [
u16
:len
] - [
len*byte
:reason
]
- [
The receiving node:
- MUST forget the corresponding
propose_funding_htlc
. - SHOULD fail any matching downstream HTLC, if any.
-
type: 33002 (
accept_funding_htlcs
) -
data:
- [
chain_hash
:chain_hash
] - [
u64
:max_funding_fee_msat
]
- [
-
tlv_stream
:accept_funding_htlcs_tlvs
-
types:
- type: 0 (
funding_rates
) - data:
- [
u32
:fee_base_sat
] - [
u16
:fee_proportional_basis
]
- [
- type: 1 (
proposed_htlcs
) - data:
- [
...*32*byte
:payment_hashes
]
- [
- type: 0 (
The sending node:
- MUST set
funding_rates
to the last receivedfunding_rates
from the remote node'spropose_funding_htlc
. - MUST set
max_funding_fee_msat
to the maximum fee it is prepared to pay for additional inbound liquidity. - MUST include that
payment_hash
of all pendingpropose_funding_htlc
inproposed_htlcs
.
The receiving node:
- If
max_funding_fee_msat
is too low:- SHOULD send a
warning
and disconnect.
- SHOULD send a
- If
funding_rates
don't match the latest rates:- SHOULD send a
warning
and disconnect.
- SHOULD send a
- Otherwise:
- MUST initiate the funding flow to add more liquidity towards the sending
nodes, using the provided
funding_rates
.
- MUST initiate the funding flow to add more liquidity towards the sending
nodes, using the provided
The recipient of a payment sends accept_funding_htlcs
to let the other node
know that they can proceed with adding more liquidity to forward HTLCs matching
the provided payment_hashes
. They echo the last received funding_rates
to
acknowledge that they accept those rates and will allow the corresponding fees
to be deduced from future HTLCs forwarded on that channel.
There is a proportional fee based on the amount that the other node will fund.
The sender can put a threshold on that amount by setting max_funding_fee_msat
which implicitly tells the other node to avoid adding too much liquidity, or to
add it without additional fees.
When nodes cannot agree on funding parameters, they disconnect, which resets the state.
-
tlv_stream
:open_channel2_tlvs
-
types:
- type: 65537 (
funding_fee
) - data:
- [
u64
:funding_fee_msat
]
- [
- type: 65537 (
-
tlv_stream
:splice_init_tlvs
-
types:
- type: 65537 (
funding_fee
) - data:
- [
u64
:funding_fee_msat
]
- [
- type: 65537 (
The sending node:
- If it is responding to
accept_funding_htlcs
:- MUST set
funding_fee
to the fee it will collect from future HTLCs for the liquidity it is providing to the receiving node. - MAY include the on-chain fee it will pay for the funding transaction.
- MUST NOT exceed the
max_funding_fee_msat
fromaccept_funding_htlcs
.
- MUST set
The receiving node:
- If
funding_fee
is not acceptable:- MUST send
error
to reject the funding attempt.
- MUST send
- Once the corresponding funding transaction has been confirmed:
- MUST allow future HTLCs to subtract
funding_fee_msat
from their amount.
- MUST allow future HTLCs to subtract
When adding on-the-fly channel liquidity, the receiving node may not have any way of paying fees upfront. Those fees are instead collected from the future HTLCs forwarded on that channel.
tlv_stream
:update_add_htlc_tlvs
- types:
- type: 65537 (
funding_fee
) - data:
- [
u64
:funding_fee_msat
]
- [
- type: 65537 (
The sending node:
- If it recently funded a channel in response to
accept_funding_htlcs
, and hasn't fully collected the corresponding fees:- SHOULD set
funding_fee
to a value smaller than or equal to the remaining fees it should collect. - MUST deduce
funding_fee
from theamount_msat
it would have otherwise sent. - MAY set
funding_fee
to a fraction of the fees it needs to collect.
- SHOULD set
The receiving node:
- If
funding_fee
is set:- If
funding_fee
is greater than the remaining fees it owes the sending node for the channel liquidity that was provided:- SHOULD send a
warning
. - MUST fail the HTLC.
- SHOULD send a
- MUST handle the HTLC as if its amount was
amount_msat + funding_fee
.
- If
Funding nodes get paid for the liquidity they provide by subtracting their fees
from HTLCs. It is possible to spread out the fees across multiple HTLCs (e.g.
in case MPP was used). The receiving node must keep track of its remaining debt
and accept funding_fee
accordingly.
The node that is funding additional liquidity is taking a risk, because it is
funding upfront and getting paid the corresponding fees later. A malicious
recipient would send accept_funding_htlcs
, wait for the additional inbound
liquidity, and then fail HTLCs that set a funding_fee
.
When that happens, the funding node should keep subtracting the funding_fee
from the next HTLCs it forwards. The receiving node must eventually accept
them, otherwise it won't receive anything, which makes the attack worthless.
If the receiving node keeps failing HTLCs, the funding node should eventually close the channel and blacklist the receiving node. The funding node may also double-spend the funding transaction, if it wasn't confirmed, which avoids the additional closing on-chain fee.
The following examples can also act as test vectors.
Alice Bob Carol
| update_add_htlc | |
| amount = 50_100_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 50_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | open_channel2 |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | accept_channel2 |
| |<-------------------------------|
| | channel_ready |
| |------------------------------->|
| | channel_ready |
| |<-------------------------------|
| | update_add_htlc |
| | amount = 49_000_000 msat |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
Alice Bob Carol
| update_add_htlc | |
| amount = 30_100_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 30_000_000 msat |
| |------------------------------->|
| update_add_htlc | |
| amount = 20_050_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 20_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | open_channel2 |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | accept_channel2 |
| |<-------------------------------|
| | channel_ready |
| |------------------------------->|
| | channel_ready |
| |<-------------------------------|
| | update_add_htlc |
| | amount = 49_000_000 msat |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
| update_fulfill_htlc | |
|<---------------------------| |
Alice Bob Carol
| update_add_htlc | |
| amount = 30_100_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | proposed_htlc_id = 1 |
| | amount = 30_000_000 msat |
| |------------------------------->|
| update_add_htlc | |
| amount = 10_500_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | proposed_htlc_id = 2 |
| | amount = 10_000_000 msat |
| |------------------------------->|
| | fail_funding_htlc |
| | proposed_htlc_id = 2 |
| |<-------------------------------|
| update_fail_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 20_050_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | proposed_htlc_id = 3 |
| | amount = 20_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | splice_init |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | splice_ack |
| |<-------------------------------|
| | channel_ready |
| |------------------------------->|
| | channel_ready |
| |<-------------------------------|
| | update_add_htlc |
| | amount = 49_000_000 msat |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
| update_fulfill_htlc | |
|<---------------------------| |
Alice Bob Carol
| update_add_htlc | |
| amount = 50_100_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 50_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | splice_init |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | splice_ack |
| |<-------------------------------|
| | channel_ready |
| |------------------------------->|
| | channel_ready |
| |<-------------------------------|
| | update_add_htlc |
| | amount = 49_000_000 msat |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | update_fail_htlc |
| |<-------------------------------|
| update_fail_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 20_200_000 msat | |
|--------------------------->| |
| | update_add_htlc |
| | amount = 19_500_000 msat |
| | funding_fee = 500_000 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 10_001_000 msat | |
|--------------------------->| |
| | update_add_htlc |
| | amount = 9_500_000 msat |
| | funding_fee = 500_000 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
Alice Bob Carol
| update_add_htlc | |
| amount = 50_100_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 50_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| | X----------------|
| update_fail_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 25_100_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 25_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | splice_init |
| | funding_fee = 1_000_000 msat |
| |---------------X |
| update_fail_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 75_500_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 75_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | splice_init |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | splice_ack |
| | X----------------|
| update_fail_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 30_200_000 msat | |
|--------------------------->| |
| | propose_funding_htlc |
| | amount = 30_000_000 msat |
| |------------------------------->|
| | accept_funding_htlcs |
| |<-------------------------------|
| | splice_init |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | splice_ack |
| |<-------------------------------|
| | channel_ready |
| |------------------------------->|
| | channel_ready |
| |<-------------------------------|
| | update_add_htlc |
| | amount = 29_000_000 msat |
| | funding_fee = 1_000_000 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
| update_add_htlc | |
| amount = 10_050_000 msat | |
|--------------------------->| |
| | update_add_htlc |
| | amount = 10_000_000 msat |
| | funding_fee = 0 msat |
| |------------------------------->|
| | update_fulfill_htlc |
| |<-------------------------------|
| update_fulfill_htlc | |
|<---------------------------| |
Happened across this by accident today, but since I'm here...
I really like this idea, and think it's a useful thing to add to the protocol. I had one suggestion.
Instead of adding two new messages (
proposed_funding_htlc
/accept_funding_htlc
) do you think you could add data toopen_channel2
that includes the proposal of the htlc instead?We use the
commitment_signed
message for the initial commitment transactions in splicing/openchannel2, which includes space for htlcs. You could add the offered htlc to the first commitment transaction of that channel, which gets negotiated/signed before the funds are committed to it.I think this simplifies the flow a lot, plus lets you immediately commit to the offered htlc during the 'negotiation phase' of the channel open. If your peer doesn't send you an initial commitment transaction with the htlc already built into it, you can easily cancel/abort the open negotiation, before any funds get sent/committed to chain. You can also account for the
funding_fee
in the opening balance of the channel as well (maybe with an option to "prepay" out of the initial HTLC if desired?)I think doing this removes 3 messages (propose/accept funding htlc + initial update_add_htlc), and insteads adds extra data to the openchannel/splice TLVS.
I also really like the idea of having
openchannel
include any 'negotiation' details. This would closely match the negotiation behavior that we use for the current liquidity ads specification. (openchannel/acceptchannel as the call/response with an offer + either acceptance or refusal)The receiving/end node would then just wait until after
channel_ready
to sendupdate_fulfill_htlc
(which iiuc would be ~immediately if zerconf is flagged)