|
| 1 | +``` |
| 2 | +bLIP: 57 |
| 3 | +Title: LSPS7: Channel Lease Extensions |
| 4 | +Status: Draft |
| 5 | +Author: Evan Kaloudis <[email protected]> |
| 6 | +Created: 2025-01-10 |
| 7 | +License: MIT |
| 8 | +``` |
| 9 | + |
| 10 | +# Channel Lease Extensions (LSPS7) |
| 11 | + |
| 12 | +LSPS7 requires [LSPS0](./blip-0050.md) and therefore [BOLT8](https://github.com/lightning/bolts/blob/master/08-transport.md) as a transport layer. The payment object structure is taken from [LSPS1][]. |
| 13 | + |
| 14 | +## Motivation |
| 15 | + |
| 16 | +The goal of this specification is to provide a standardized LSP API for wallets to extend the lifetime of a channel purchased from an LSP. |
| 17 | + |
| 18 | +## Copyright |
| 19 | + |
| 20 | +This bLIP is licensed under the MIT license. |
| 21 | + |
| 22 | +## Order Flow Overview |
| 23 | + |
| 24 | +* Client calls `lsps7.get_extendable_channels` to get the LSP's options. |
| 25 | +* Client calls `lsps7.create_order` to create an order. |
| 26 | +* Client pays the order either on-chain or off-chain. |
| 27 | +* LSP extends the expiry time of the channel as soon as the payment is confirmed. |
| 28 | + |
| 29 | + |
| 30 | +## API |
| 31 | + |
| 32 | +### 1. lsps7.get_extendable_channels |
| 33 | + |
| 34 | +| JSON-RPC Method | lsps7.get_extendable_channels | |
| 35 | +|---------------- |----------- | |
| 36 | +| Idempotent | Yes | |
| 37 | + |
| 38 | + |
| 39 | +`lsps7.get_extendable_channels` is the entrypoint for each client using the API. It lists all options in a dictionary. |
| 40 | + |
| 41 | +The client SHOULD call `lsps7.get_extendable_channels` first. |
| 42 | + |
| 43 | +**Request** No parameters needed. |
| 44 | + |
| 45 | +**Response** |
| 46 | + |
| 47 | +```JSON |
| 48 | +{ |
| 49 | + "extendable_channels": [ |
| 50 | + { |
| 51 | + "original_order": { |
| 52 | + "id": "3c2ef2b-195f-0695-b72ef-3929b9138e04", |
| 53 | + "service": "LSPS1" |
| 54 | + }, |
| 55 | + "extension_order_ids": [ |
| 56 | + "142b3ef8-2eca-6135-7f74-7d1afda8b835", |
| 57 | + "39afg2a3-7cf7-f3b6-056e-43f0d5b61f3f" |
| 58 | + ], |
| 59 | + "short_channel_id": "871428x964x0", |
| 60 | + "max_channel_extension_expiry_blocks": 300, |
| 61 | + "expiration_block": 839230 |
| 62 | + } |
| 63 | + ] |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +`extendable_channels` is an array of channels the LSP will allow you to extend the expiry of. The structure of the `extendable_channel` object can be found in section [3. Extendable Channel](#3-extendable-channel). |
| 68 | + |
| 69 | + |
| 70 | +**Errors** No additional errors are defined for this method. |
| 71 | + |
| 72 | +### 2. lsps7.create_order |
| 73 | + |
| 74 | +| JSON-RPC Method | lsps7.create_order | |
| 75 | +|-------------------- |------------------- | |
| 76 | +| Idempotent | No | |
| 77 | + |
| 78 | + |
| 79 | +The request is constructed depending on the client's needs. |
| 80 | + |
| 81 | +**Request** |
| 82 | + |
| 83 | +```json |
| 84 | +{ |
| 85 | + "short_channel_id": "871428x964x0", |
| 86 | + "channel_extension_expiry_blocks": 144, |
| 87 | + "token": "", |
| 88 | + "refund_onchain_address": "bc1qvmsy0f3yyes6z9jvddk8xqwznndmdwapvrc0xrmhd3vqj5rhdrrq6hz49h" |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +- `short_channel_id` <[LSPS0.scid][]> Short Channel Identifier (SCID) of the channel to extend. |
| 93 | +- `channel_extension_expiry_blocks <uint32>` How long to extend the lease of the channel in block time. |
| 94 | + - MUST be 1 or greater. |
| 95 | + - MUST be below or equal to `max_channel_extension_expiry_blocks` returned from the `get_extendable_channels` call. |
| 96 | +- `token <string>` Field for arbitrary data like a coupon code or a authentication token. |
| 97 | + - Client MAY omit this field. |
| 98 | +- `refund_onchain_address <string>` <LSPS0.onchain_address> Address where the LSP will send the funds if the order fails. |
| 99 | + - Client MAY omit this field. |
| 100 | + |
| 101 | +**Response** |
| 102 | + |
| 103 | +```json |
| 104 | +{ |
| 105 | + "order_id": "bb4b5d0a-8334-49d8-9463-90a6d413af7c", |
| 106 | + "channel_extension_expiry_blocks": 144, |
| 107 | + "new_channel_expiry_blocks": 839374, |
| 108 | + "token": "", |
| 109 | + "created_at": "2012-04-23T18:25:43.511Z", |
| 110 | + "order_state": "CREATED", |
| 111 | + "payment": { |
| 112 | + "bolt11": { |
| 113 | + "state": "EXPECT_PAYMENT", |
| 114 | + "expires_at": "2015-01-25T19:29:44.612Z", |
| 115 | + "fee_total_sat": "8888", |
| 116 | + "order_total_sat": "2008888", |
| 117 | + "invoice" : "lnbc252u1p3aht9ysp580g4633gd2x9lc5al0wd8wx0mpn9748jeyz46kqjrpxn52uhfpjqpp5qgf67tcqmuqehzgjm8mzya90h73deafvr4m5705l5u5l4r05l8cqdpud3h8ymm4w3jhytnpwpczqmt0de6xsmre2pkxzm3qydmkzdjrdev9s7zhgfaqxqyjw5qcqpjrzjqt6xptnd85lpqnu2lefq4cx070v5cdwzh2xlvmdgnu7gqp4zvkus5zapryqqx9qqqyqqqqqqqqqqqcsq9q9qyysgqen77vu8xqjelum24hgjpgfdgfgx4q0nehhalcmuggt32japhjuksq9jv6eksjfnppm4hrzsgyxt8y8xacxut9qv3fpyetz8t7tsymygq8yzn05" |
| 118 | + }, |
| 119 | + "onchain": { |
| 120 | + "state": "EXPECT_PAYMENT", |
| 121 | + "expires_at": "2015-01-25T19:29:44.612Z", |
| 122 | + "fee_total_sat": "9999", |
| 123 | + "order_total_sat": "2009999", |
| 124 | + "address" : "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr", |
| 125 | + "min_fee_for_0conf": 253, |
| 126 | + "min_onchain_payment_confirmations": 0, |
| 127 | + "refund_onchain_address": "bc1qvmsy0f3yyes6z9jvddk8xqwznndmdwapvrc0xrmhd3vqj5rhdrrq6hz49h" |
| 128 | + } |
| 129 | + }, |
| 130 | + "channel": { |
| 131 | + "original_order": { |
| 132 | + "id": "3c2ef2b-195f-0695-b72ef-3929b9138e04", |
| 133 | + "service": "LSPS1" |
| 134 | + }, |
| 135 | + "extension_order_ids": [ |
| 136 | + "142b3ef8-2eca-6135-7f74-7d1afda8b835", |
| 137 | + "39afg2a3-7cf7-f3b6-056e-43f0d5b61f3f" |
| 138 | + ], |
| 139 | + "short_channel_id": "871428x964x0", |
| 140 | + "max_channel_extension_expiry_blocks": 300, |
| 141 | + "expiration_block": 839230 |
| 142 | + }, |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +- `order_id <string>` Id of this specific order. |
| 147 | + - MUST be unique. |
| 148 | + - MUST be at most 64 characters long. |
| 149 | + - SHOULD be a valid [UUID version 4](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)) (aka random UUID). |
| 150 | +- `funding_confirms_within_blocks <uint16>` Mirrored from the request. |
| 151 | +- `token <string>` Mirrored from the request. |
| 152 | + - MUST be an empty string if the token was not provided. |
| 153 | +- `created_at` <[LSPS0.datetime][]> Datetime when the order was created. |
| 154 | +- `order_state <string enum>` Current state of the order. |
| 155 | + - `CREATED` Order has been created. Default value. |
| 156 | + - `COMPLETED` LSP has published funding transaction. |
| 157 | + - `FAILED` Order failed. |
| 158 | +- `payment <object>` Contains everything about payments, see [4. Payment](#4-payment). |
| 159 | +- `channel <object>` Contains information about the channel, see [3. Extendable Channel](#3-extendable-channel). |
| 160 | + |
| 161 | + |
| 162 | +**Client** |
| 163 | +- SHOULD validate that `channel_extension_expiry_blocks` + `channel_extension_expiry_blocks` = `new_channel_expiry_blocks`. |
| 164 | +- SHOULD validate the `fee_total_sat` is reasonable. |
| 165 | +- MAY abort the flow here. |
| 166 | + |
| 167 | +**Errors** |
| 168 | + |
| 169 | +| Code | Message | Data | Description | |
| 170 | +| ---- | ------- | ----------- | ---- | |
| 171 | +| -32602 | Invalid params | {"property": %invalid_property%, "message": %human_message% } | Invalid method parameter(s). | |
| 172 | +| 001 | Client rejected | {"message": %human_message% } | [LSPS0.client_rejected_error][] | |
| 173 | +| 100 | Option mismatch | {"property": %option_mismatch_property%, "message": %human_message% } | The order doesnt match the options defined in `lsps7.get_extendable_channels` channel object. | |
| 174 | + |
| 175 | + |
| 176 | +- LSP MUST validate the order against the options defined in the `lsps7.get_extendable_channels` channel object. LSP MUST return an `100` error in case of a mismatch. |
| 177 | + - `%option_mismatch_property%` MUST be one of the fields in the `lsps7.get_extendable_channels` channel object. |
| 178 | + - Example: `{ "property": "max_channel_extension_expiry_blocks" }`. |
| 179 | + |
| 180 | +- LSP MUST validate the request fields. LSP MUST return a `-32602` error in case of an invalid request field. |
| 181 | + - `%invalid_property%` MUST be one of the fields in the request body. MUST use `.` to separate nested fields. |
| 182 | + - Example: `{ "property": "channel_extension_expiry_blocks", "message": "Not an integer" }`. |
| 183 | + |
| 184 | +- LSP MUST validate the `token` field and return an error if the token is invalid. |
| 185 | + |
| 186 | +> **Rationale `token` validation** The client should be informed if the token is invalid. Ignoring the invalid token and creating an order without the potentially discount or other side effect is not good UX. Ignoring the invalid token will also NOT prevent anybody bruteforcing the token because the client will still detect if the LSP has given a discount. |
| 187 | +
|
| 188 | +> **Rationale Client rejected** LSPs can reject a client for example for misbehaviour. LSPs can reject a node on two levels: Prevent a peer connection OR disable order creation. Preventing a peer connection might not work in case you still want to allow other functions to keep working, for example an existing channel. |
| 189 | +
|
| 190 | + |
| 191 | +### 2.1 lsps7.get_order |
| 192 | + |
| 193 | +| JSON-RPC Method | lsps7.get_order | |
| 194 | +|---------------- |---------------- | |
| 195 | +| Idempotent | Yes | |
| 196 | + |
| 197 | +The client MAY check the current status of the order at any point. |
| 198 | + |
| 199 | +**Request** |
| 200 | + |
| 201 | +```json |
| 202 | +{ |
| 203 | + "order_id": "bb4b5d0a-8334-49d8-9463-90a6d413af7c" |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +**Response** is the same as defined in `lsps7.create_order`. |
| 208 | + |
| 209 | +**Errors** |
| 210 | + |
| 211 | +| Code | Message | Data | Description | |
| 212 | +| ------ | --------- | ------- | ----------------------------------------------------- | |
| 213 | +| 101 | Not found | {} | Order with the requested order_id has not been found. | |
| 214 | + |
| 215 | + |
| 216 | +### 3. Extendable channel |
| 217 | + |
| 218 | +The `extendable_channel` object returned in `lsps7.get_extendable_channels`, `lsps7.create_order`, and `lsps7.get_order`, has the following properties: |
| 219 | + |
| 220 | +- `original_order <object>` An *optional* property for the original order for the channel lease, including the `id <string>` to identify the order and the `service <string>` the purchase occured on. |
| 221 | +- `extension_order_ids <string[]>` An *optional* list of order IDs for each time the channel lease has been extended. |
| 222 | +- `short_channel_id` <[LSPS0.scid][]> Short Channel Identifier (SCID) of the channel. |
| 223 | +- `max_channel_extension_expiry_blocks <uint32>` The maximum number of blocks a channel can be leased for. |
| 224 | +- `expiration_block <uint32>` The block height at which the channel lease will expiry. May be marked as 0 if the user opened the channel to the LSP, and the LSP has no obligation to keep open. |
| 225 | + - MUST be 0 or greater. |
| 226 | + |
| 227 | +> **Rationale `original_order`** The client MAY want to look up the original order for reference. |
| 228 | +
|
| 229 | +> **Rationale `extension_order_ids`** The client MAY want to look up any one of the previous extensions orders for reference. |
| 230 | +
|
| 231 | +### 4. Payment |
| 232 | + |
| 233 | +The `payment` object returned by `lsps7.create_order` and `lsps7.get_order`, mirrors the formatting of [LSPS1.payment][]. |
| 234 | + |
| 235 | + |
| 236 | +[LSPS0.onchain_address]: ./blip-0050.md#link-lsps0onchain_address |
| 237 | +[LSPS0.datetime]: ./blip-0050.md#link-lsps0datetime\ |
| 238 | +[LSPS0.scid]: ./blip-0050.md#link-lsps0scid |
| 239 | +[LSPS0.client_rejected_error]: ./blip-0050.md#link-lsps0client_rejected_error |
| 240 | +[LSPS1]: ./blip-0051.md |
| 241 | +[LSPS1.payment]: ./blip-0051.md#3-payment |
0 commit comments