Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions blip-0052.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Example `lsps2.get_info` result:
{
"min_fee_msat": "546000",
"proportional": 1200,
"ongoing_proportional": 1000,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't just add a field here as it would render all historical promises invalid. If we want to make such a change, it needs to be backwards compatible (read: additive), e.g., you could add a flag to the get_info result that would indicate that the agreed upon fees will be withheld on every forward, and then the service would need to reject any buy requests that have that field unset (i.e., if they don't know it).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Pushed new commit that moved it to a flag outside of the params instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, as I went to implement this in ldk and ldk-node I realized that it's tough to avoid rendering historical promises invalid. Even if we move ongoing_proportional outside the opening_fee_params then we still would ideally want to include it in the generation of the promise.

If we do not include it in the promise then the LSP has no way to know what value it gave out as a response to a get_info request and cannot validate the value provided in the buy request.

The alternatives would be:

  1. Store a mapping from promise to ongoing_proportional for each promise given out. This seems like not a great idea as the whole point of the promise is for the LSP to remain stateless when handling get_info requests.

  2. Use a static ongoing_proportional value across all requests. This is less flexible but maybe acceptable (if it's the only option)? Seems like it might run into issues should the LSP ever want to change it as someone who received a get_info request with the old value will run into unexpected behavior should they issue a buy request after the new value is set by the LSP. Even if you require the client to send the value they received to the user, the LSP cannot trust it because it's not included in the promise.

Any thoughts?

Copy link

@JssDWt JssDWt Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had made a PR before that allows making backward compatible changes to the calculation of the promise for the opening_fee_params. It was discussed then that if we want to add fields to the opening_fee_params, that would be a good time to revisit. This is the PR in question: BitcoinAndLightningLayerSpecs/lsp#111

Let me know if that helps your case.

"valid_until": "2023-02-23T08:47:30.511Z",
"min_lifetime": 1008,
"max_client_to_self_delay": 2016,
Expand All @@ -234,6 +235,7 @@ Example `lsps2.get_info` result:
{
"min_fee_msat": "1092000",
"proportional": 2400,
"ongoing_proportional": 500,
"valid_until": "2023-02-27T21:23:57.984Z",
"min_lifetime": 1008,
"max_client_to_self_delay": 2016,
Expand All @@ -250,8 +252,8 @@ The LSP MAY return an empty array; in which case, the client currently
cannot use JIT Channels with this LSP.

An `opening_fee_params` object describes how much the LSP will charge for a
channel open, what payment sizes it will accept, and until when the described
parameters will be considered valid.
channel open, what payment sizes it will accept, ongoing fees for subsequent
payments, and until when the described parameters will be considered valid.
An `opening_fee_params` object MUST have all of the following fields,
and MUST NOT have any additional fields:

Expand All @@ -267,6 +269,12 @@ and MUST NOT have any additional fields:
`min_fee_msat` is paid instead of the proportional times payment size
divided by 1 million.
[<LSPS0.ppm>][]
* `ongoing_proportional` is a parts-per-million number that describes how many
millisatoshis to charge for every 1 million millisatoshis of payment
size for each payment after the first payment that opened the channel.
This fee is charged on every forwarded payment through the channel after
the initial channel-opening payment.
[<LSPS0.ppm>][]
* `valid_until` is a datetime (as an ISO8601 string) up to which this specific
`opening_fee_params` is valid, and also serves as the timeout for the JIT
Channel flow, if this particular object is selected.
Expand Down Expand Up @@ -294,8 +302,8 @@ and MUST NOT have any additional fields:
to the client, not including the forwarding fees of nodes along the way.
* `promise` is an arbitrary LSP-generated string that proves to the LSP that
it has promised a specific `opening_fee_params` with the specific
`min_fee_msat`, `proportional`, `valid_until`, `min_lifetime`,
`max_client_to_self_delay`, `min_payment_size_msat`, and
`min_fee_msat`, `proportional`, `ongoing_proportional`, `valid_until`,
`min_lifetime`, `max_client_to_self_delay`, `min_payment_size_msat`, and
`max_payment_size_msat`.

> **Rationale** A "minimum" fee is used instead of an additive base fee as
Expand Down Expand Up @@ -553,6 +561,7 @@ Example `lsps2.buy` request parameters:
"opening_fee_params": {
"min_fee_msat": "546000",
"proportional": 1200,
"ongoing_proportional": 1000,
"valid_until": "2023-02-23T08:47:30.511Z",
"min_lifetime": 1008,
"max_client_to_self_delay": 2016,
Expand Down Expand Up @@ -1029,6 +1038,33 @@ selected.
LSPs MUST consider this alias not just for forwarded payments, but
also for onion messages.

#### Ongoing Proportional Fees

For all payments forwarded through the channel after the initial
channel-opening payment, the LSP MUST charge an ongoing proportional
fee as specified in the `ongoing_proportional` field from the
`opening_fee_params` used to open the channel.

The ongoing fee for each payment is computed as:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should repeat the computation here, but rather just refer to the other parts of the spec.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only potential confusion is that in the 'computing the fee' section that is for the opening fee which includes the min_fee portion. The ongoing_fee as defined only includes the proportional component of the fee.


ongoing_fee = ((payment_amount_msat * ongoing_proportional) + 999999) / 1000000

Where `payment_amount_msat` is the amount being forwarded to the client,
and `ongoing_proportional` is the value from the `opening_fee_params`.

* All numbers MUST be computed using the same overflow detection rules
specified in the "Computing The `opening_fee`" section.
* The LSP MUST deduct this `ongoing_fee` from each forwarded payment.
* The LSP MUST include an `extra_fee` TLV (type 65537) in the
`update_add_htlc` message for each payment that has ongoing fees
deducted, with the value set to the computed `ongoing_fee`.
* The client MUST accept these fee deductions and validate that the
`extra_fee` matches the expected `ongoing_fee` calculation.

This ongoing fee allows LSPs to distribute the cost of channel opening
across the lifetime of the channel rather than charging the full amount
upfront on the first payment.

[<LSPS0.msat>]: ./blip-0050.md#link-lsps0msat
[<LSPS0.ppm>]: ./blip-0050.md#link-lsps0ppm
[<LSPS0.datetime>]: ./blip-0050.md#link-lsps0datetime
Expand Down