lightning_liquidity/lsps1/
msgs.rs

1//! Message, request, and other primitive types used to implement LSPS1.
2
3use crate::lsps0::ser::{
4	string_amount, u32_fee_rate, unchecked_address, unchecked_address_option, LSPSMessage,
5	RequestId, ResponseError,
6};
7
8use crate::prelude::String;
9
10use bitcoin::{Address, FeeRate, OutPoint};
11
12use lightning_invoice::Bolt11Invoice;
13
14use serde::{Deserialize, Serialize};
15
16use chrono::Utc;
17
18use core::convert::TryFrom;
19
20pub(crate) const LSPS1_GET_INFO_METHOD_NAME: &str = "lsps1.get_info";
21pub(crate) const LSPS1_CREATE_ORDER_METHOD_NAME: &str = "lsps1.create_order";
22pub(crate) const LSPS1_GET_ORDER_METHOD_NAME: &str = "lsps1.get_order";
23
24pub(crate) const _LSPS1_CREATE_ORDER_REQUEST_INVALID_PARAMS_ERROR_CODE: i32 = -32602;
25#[cfg(lsps1_service)]
26pub(crate) const LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE: i32 = 100;
27
28/// The identifier of an order.
29#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)]
30pub struct OrderId(pub String);
31
32/// A request made to an LSP to retrieve the supported options.
33///
34/// Please refer to the [LSPS1 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1#1-lsps1info)
35/// for more information.
36#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
37#[serde(default)]
38pub struct GetInfoRequest {}
39
40/// An object representing the supported protocol options.
41#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
42pub struct LSPS1Options {
43	/// The smallest number of confirmations needed for the LSP to accept a channel as confirmed.
44	pub min_required_channel_confirmations: u16,
45	/// The smallest number of blocks in which the LSP can confirm the funding transaction.
46	pub min_funding_confirms_within_blocks: u16,
47	/// Indicates if the LSP supports zero reserve.
48	pub supports_zero_channel_reserve: bool,
49	/// The maximum number of blocks a channel can be leased for.
50	pub max_channel_expiry_blocks: u32,
51	/// The minimum number of satoshi that the client MUST request.
52	#[serde(with = "string_amount")]
53	pub min_initial_client_balance_sat: u64,
54	/// The maximum number of satoshi that the client MUST request.
55	#[serde(with = "string_amount")]
56	pub max_initial_client_balance_sat: u64,
57	/// The minimum number of satoshi that the LSP will provide to the channel.
58	#[serde(with = "string_amount")]
59	pub min_initial_lsp_balance_sat: u64,
60	/// The maximum number of satoshi that the LSP will provide to the channel.
61	#[serde(with = "string_amount")]
62	pub max_initial_lsp_balance_sat: u64,
63	/// The minimal channel size.
64	#[serde(with = "string_amount")]
65	pub min_channel_balance_sat: u64,
66	/// The maximal channel size.
67	#[serde(with = "string_amount")]
68	pub max_channel_balance_sat: u64,
69}
70
71/// A response to a [`GetInfoRequest`].
72#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
73pub struct GetInfoResponse {
74	/// All options supported by the LSP.
75	#[serde(flatten)]
76	pub options: LSPS1Options,
77}
78
79/// A request made to an LSP to create an order.
80///
81/// Please refer to the [LSPS1 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1#2-lsps1create_order)
82/// for more information.
83#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
84pub struct CreateOrderRequest {
85	/// The order made.
86	#[serde(flatten)]
87	pub order: OrderParameters,
88	/// The address where the LSP will send the funds if the order fails.
89	#[serde(default)]
90	#[serde(skip_serializing_if = "Option::is_none")]
91	#[serde(with = "unchecked_address_option")]
92	pub refund_onchain_address: Option<Address>,
93}
94
95/// An object representing an LSPS1 channel order.
96#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
97pub struct OrderParameters {
98	/// Indicates how many satoshi the LSP will provide on their side.
99	#[serde(with = "string_amount")]
100	pub lsp_balance_sat: u64,
101	/// Indicates how many satoshi the client will provide on their side.
102	///
103	/// The client sends these funds to the LSP, who will push them back to the client upon opening
104	/// the channel.
105	#[serde(with = "string_amount")]
106	pub client_balance_sat: u64,
107	/// The number of confirmations the funding tx must have before the LSP sends `channel_ready`.
108	pub required_channel_confirmations: u16,
109	/// The maximum number of blocks the client wants to wait until the funding transaction is confirmed.
110	pub funding_confirms_within_blocks: u16,
111	/// Indicates how long the channel is leased for in block time.
112	pub channel_expiry_blocks: u32,
113	/// May contain arbitrary associated data like a coupon code or a authentication token.
114	pub token: Option<String>,
115	/// Indicates if the channel should be announced to the network.
116	pub announce_channel: bool,
117}
118
119/// A response to a [`CreateOrderRequest`].
120#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
121pub struct CreateOrderResponse {
122	/// The id of the channel order.
123	pub order_id: OrderId,
124	/// The parameters of channel order.
125	#[serde(flatten)]
126	pub order: OrderParameters,
127	/// The datetime when the order was created
128	pub created_at: chrono::DateTime<Utc>,
129	/// The current state of the order.
130	pub order_state: OrderState,
131	/// Contains details about how to pay for the order.
132	pub payment: PaymentInfo,
133	/// Contains information about the channel state.
134	pub channel: Option<ChannelInfo>,
135}
136
137/// An object representing the state of an order.
138#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
139#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
140pub enum OrderState {
141	/// The order has been created.
142	Created,
143	/// The LSP has opened the channel and published the funding transaction.
144	Completed,
145	/// The order failed.
146	Failed,
147}
148
149/// Details regarding how to pay for an order.
150#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
151pub struct PaymentInfo {
152	/// A Lightning payment using BOLT 11.
153	pub bolt11: Option<Bolt11PaymentInfo>,
154	/// An onchain payment.
155	pub onchain: Option<OnchainPaymentInfo>,
156}
157
158/// A Lightning payment using BOLT 11.
159#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
160pub struct Bolt11PaymentInfo {
161	/// Indicates the current state of the payment.
162	pub state: PaymentState,
163	/// The datetime when the payment option expires.
164	pub expires_at: chrono::DateTime<Utc>,
165	/// The total fee the LSP will charge to open this channel in satoshi.
166	#[serde(with = "string_amount")]
167	pub fee_total_sat: u64,
168	/// The amount the client needs to pay to have the requested channel openend.
169	#[serde(with = "string_amount")]
170	pub order_total_sat: u64,
171	/// A BOLT11 invoice the client can pay to have to channel opened.
172	pub invoice: Bolt11Invoice,
173}
174
175/// An onchain payment.
176#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
177pub struct OnchainPaymentInfo {
178	/// Indicates the current state of the payment.
179	pub state: PaymentState,
180	/// The datetime when the payment option expires.
181	pub expires_at: chrono::DateTime<Utc>,
182	/// The total fee the LSP will charge to open this channel in satoshi.
183	#[serde(with = "string_amount")]
184	pub fee_total_sat: u64,
185	/// The amount the client needs to pay to have the requested channel openend.
186	#[serde(with = "string_amount")]
187	pub order_total_sat: u64,
188	/// An on-chain address the client can send [`Self::order_total_sat`] to to have the channel
189	/// opened.
190	#[serde(with = "unchecked_address")]
191	pub address: Address,
192	/// The minimum number of block confirmations that are required for the on-chain payment to be
193	/// considered confirmed.
194	pub min_onchain_payment_confirmations: Option<u16>,
195	/// The minimum fee rate for the on-chain payment in case the client wants the payment to be
196	/// confirmed without a confirmation.
197	#[serde(with = "u32_fee_rate")]
198	pub min_fee_for_0conf: FeeRate,
199	/// The address where the LSP will send the funds if the order fails.
200	#[serde(default)]
201	#[serde(skip_serializing_if = "Option::is_none")]
202	#[serde(with = "unchecked_address_option")]
203	pub refund_onchain_address: Option<Address>,
204}
205
206/// The state of a payment.
207///
208/// *Note*: Previously, the spec also knew a `CANCELLED` state for BOLT11 payments, which has since
209/// been deprecated and `REFUNDED` should be used instead.
210#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
211#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
212pub enum PaymentState {
213	/// A payment is expected.
214	ExpectPayment,
215	/// A sufficient payment has been received.
216	Paid,
217	/// The payment has been refunded.
218	#[serde(alias = "CANCELLED")]
219	Refunded,
220}
221
222/// Details regarding a detected on-chain payment.
223#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
224pub struct OnchainPayment {
225	/// The outpoint of the payment.
226	pub outpoint: String,
227	/// The amount of satoshi paid.
228	#[serde(with = "string_amount")]
229	pub sat: u64,
230	/// Indicates if the LSP regards the transaction as sufficiently confirmed.
231	pub confirmed: bool,
232}
233
234/// Details regarding the state of an ordered channel.
235#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
236pub struct ChannelInfo {
237	/// The datetime when the funding transaction has been published.
238	pub funded_at: chrono::DateTime<Utc>,
239	/// The outpoint of the funding transaction.
240	pub funding_outpoint: OutPoint,
241	/// The earliest datetime when the channel may be closed by the LSP.
242	pub expires_at: chrono::DateTime<Utc>,
243}
244
245/// A request made to an LSP to retrieve information about an previously made order.
246///
247/// Please refer to the [LSPS1 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1#21-lsps1get_order)
248/// for more information.
249#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
250pub struct GetOrderRequest {
251	/// The id of the order.
252	pub order_id: OrderId,
253}
254
255/// An enum that captures all the valid JSON-RPC requests in the LSPS1 protocol.
256#[derive(Clone, Debug, PartialEq, Eq)]
257pub enum LSPS1Request {
258	/// A request to learn about the options supported by the LSP.
259	GetInfo(GetInfoRequest),
260	/// A request to create a channel order.
261	CreateOrder(CreateOrderRequest),
262	/// A request to query a previously created channel order.
263	GetOrder(GetOrderRequest),
264}
265
266/// An enum that captures all the valid JSON-RPC responses in the LSPS1 protocol.
267#[derive(Clone, Debug, PartialEq, Eq)]
268pub enum LSPS1Response {
269	/// A successful response to a [`GetInfoRequest`].
270	GetInfo(GetInfoResponse),
271	/// An error response to a [`GetInfoRequest`].
272	GetInfoError(ResponseError),
273	/// A successful response to a [`CreateOrderRequest`].
274	CreateOrder(CreateOrderResponse),
275	/// An error response to a [`CreateOrderRequest`].
276	CreateOrderError(ResponseError),
277	/// A successful response to a [`GetOrderRequest`].
278	GetOrder(CreateOrderResponse),
279	/// An error response to a [`GetOrderRequest`].
280	GetOrderError(ResponseError),
281}
282
283/// An enum that captures all valid JSON-RPC messages in the LSPS1 protocol.
284#[derive(Clone, Debug, PartialEq, Eq)]
285pub enum LSPS1Message {
286	/// An LSPS1 JSON-RPC request.
287	Request(RequestId, LSPS1Request),
288	/// An LSPS1 JSON-RPC response.
289	Response(RequestId, LSPS1Response),
290}
291
292impl TryFrom<LSPSMessage> for LSPS1Message {
293	type Error = ();
294
295	fn try_from(message: LSPSMessage) -> Result<Self, Self::Error> {
296		if let LSPSMessage::LSPS1(message) = message {
297			return Ok(message);
298		}
299
300		Err(())
301	}
302}
303
304impl From<LSPS1Message> for LSPSMessage {
305	fn from(message: LSPS1Message) -> Self {
306		LSPSMessage::LSPS1(message)
307	}
308}
309
310#[cfg(test)]
311mod tests {
312	use super::*;
313	use crate::alloc::string::ToString;
314
315	#[test]
316	fn options_supported_serialization() {
317		let min_required_channel_confirmations = 0;
318		let min_funding_confirms_within_blocks = 6;
319		let supports_zero_channel_reserve = true;
320		let max_channel_expiry_blocks = 144;
321		let min_initial_client_balance_sat = 10_000_000;
322		let max_initial_client_balance_sat = 100_000_000;
323		let min_initial_lsp_balance_sat = 100_000;
324		let max_initial_lsp_balance_sat = 100_000_000;
325		let min_channel_balance_sat = 100_000;
326		let max_channel_balance_sat = 100_000_000;
327
328		let options_supported = LSPS1Options {
329			min_required_channel_confirmations,
330			min_funding_confirms_within_blocks,
331			supports_zero_channel_reserve,
332			max_channel_expiry_blocks,
333			min_initial_client_balance_sat,
334			max_initial_client_balance_sat,
335			min_initial_lsp_balance_sat,
336			max_initial_lsp_balance_sat,
337			min_channel_balance_sat,
338			max_channel_balance_sat,
339		};
340
341		let json_str = r#"{"max_channel_balance_sat":"100000000","max_channel_expiry_blocks":144,"max_initial_client_balance_sat":"100000000","max_initial_lsp_balance_sat":"100000000","min_channel_balance_sat":"100000","min_funding_confirms_within_blocks":6,"min_initial_client_balance_sat":"10000000","min_initial_lsp_balance_sat":"100000","min_required_channel_confirmations":0,"supports_zero_channel_reserve":true}"#;
342
343		assert_eq!(json_str, serde_json::json!(options_supported).to_string());
344		assert_eq!(options_supported, serde_json::from_str(json_str).unwrap());
345	}
346
347	#[test]
348	fn parse_spec_test_vectors() {
349		// Here, we simply assert that we're able to parse all examples given in LSPS1.
350		let json_str = r#"{}"#;
351		let _get_info_request: GetInfoRequest = serde_json::from_str(json_str).unwrap();
352
353		let json_str = r#"{
354			"min_required_channel_confirmations": 0,
355			"min_funding_confirms_within_blocks" : 6,
356			"supports_zero_channel_reserve": true,
357			"max_channel_expiry_blocks": 20160,
358			"min_initial_client_balance_sat": "20000",
359			"max_initial_client_balance_sat": "100000000",
360			"min_initial_lsp_balance_sat": "0",
361			"max_initial_lsp_balance_sat": "100000000",
362			"min_channel_balance_sat": "50000",
363			"max_channel_balance_sat": "100000000"
364		}"#;
365		let _get_info_response: GetInfoResponse = serde_json::from_str(json_str).unwrap();
366
367		let json_str = r#"{
368			"lsp_balance_sat": "5000000",
369			"client_balance_sat": "2000000",
370			"required_channel_confirmations" : 0,
371			"funding_confirms_within_blocks": 6,
372			"channel_expiry_blocks": 144,
373			"token": "",
374			"refund_onchain_address": "bc1qvmsy0f3yyes6z9jvddk8xqwznndmdwapvrc0xrmhd3vqj5rhdrrq6hz49h",
375			"announce_channel": true
376		}"#;
377		let _create_order_request: CreateOrderRequest = serde_json::from_str(json_str).unwrap();
378
379		let json_str = r#"{
380			"state" : "EXPECT_PAYMENT",
381			"expires_at": "2025-01-01T00:00:00Z",
382			"fee_total_sat": "8888",
383			"order_total_sat": "200888",
384			"invoice": "lnbc252u1p3aht9ysp580g4633gd2x9lc5al0wd8wx0mpn9748jeyz46kqjrpxn52uhfpjqpp5qgf67tcqmuqehzgjm8mzya90h73deafvr4m5705l5u5l4r05l8cqdpud3h8ymm4w3jhytnpwpczqmt0de6xsmre2pkxzm3qydmkzdjrdev9s7zhgfaqxqyjw5qcqpjrzjqt6xptnd85lpqnu2lefq4cx070v5cdwzh2xlvmdgnu7gqp4zvkus5zapryqqx9qqqyqqqqqqqqqqqcsq9q9qyysgqen77vu8xqjelum24hgjpgfdgfgx4q0nehhalcmuggt32japhjuksq9jv6eksjfnppm4hrzsgyxt8y8xacxut9qv3fpyetz8t7tsymygq8yzn05"
385		}"#;
386		let _bolt11_payment: Bolt11PaymentInfo = serde_json::from_str(json_str).unwrap();
387
388		let json_str = r#"{
389			"state": "EXPECT_PAYMENT",
390			"expires_at": "2025-01-01T00:00:00Z",
391			"fee_total_sat": "9999",
392			"order_total_sat": "200999",
393			"address": "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr",
394			"min_onchain_payment_confirmations": 1,
395			"min_fee_for_0conf": 253
396		}"#;
397		let _onchain_payment: OnchainPaymentInfo = serde_json::from_str(json_str).unwrap();
398
399		let json_str = r#"{
400			"bolt11": {
401				"state" : "EXPECT_PAYMENT",
402				"expires_at": "2025-01-01T00:00:00Z",
403				"fee_total_sat": "8888",
404				"order_total_sat": "200888",
405				"invoice": "lnbc252u1p3aht9ysp580g4633gd2x9lc5al0wd8wx0mpn9748jeyz46kqjrpxn52uhfpjqpp5qgf67tcqmuqehzgjm8mzya90h73deafvr4m5705l5u5l4r05l8cqdpud3h8ymm4w3jhytnpwpczqmt0de6xsmre2pkxzm3qydmkzdjrdev9s7zhgfaqxqyjw5qcqpjrzjqt6xptnd85lpqnu2lefq4cx070v5cdwzh2xlvmdgnu7gqp4zvkus5zapryqqx9qqqyqqqqqqqqqqqcsq9q9qyysgqen77vu8xqjelum24hgjpgfdgfgx4q0nehhalcmuggt32japhjuksq9jv6eksjfnppm4hrzsgyxt8y8xacxut9qv3fpyetz8t7tsymygq8yzn05"
406			},
407			"onchain": {
408				"state": "EXPECT_PAYMENT",
409				"expires_at": "2025-01-01T00:00:00Z",
410				"fee_total_sat": "9999",
411				"order_total_sat": "200999",
412				"address": "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr",
413				"min_onchain_payment_confirmations": 1,
414				"min_fee_for_0conf": 253
415			}
416		}"#;
417		let _payment: PaymentInfo = serde_json::from_str(json_str).unwrap();
418
419		let json_str = r#"{
420			"order_id": "bb4b5d0a-8334-49d8-9463-90a6d413af7c",
421			"lsp_balance_sat": "5000000",
422			"client_balance_sat": "2000000",
423			"required_channel_confirmations" : 0,
424			"funding_confirms_within_blocks": 1,
425			"channel_expiry_blocks": 12,
426			"token": "",
427			"created_at": "2012-04-23T18:25:43.511Z",
428			"announce_channel": true,
429			"order_state": "CREATED",
430			"payment": {
431				"bolt11": {
432					"state": "EXPECT_PAYMENT",
433					"expires_at": "2015-01-25T19:29:44.612Z",
434					"fee_total_sat": "8888",
435					"order_total_sat": "2008888",
436					"invoice" : "lnbc252u1p3aht9ysp580g4633gd2x9lc5al0wd8wx0mpn9748jeyz46kqjrpxn52uhfpjqpp5qgf67tcqmuqehzgjm8mzya90h73deafvr4m5705l5u5l4r05l8cqdpud3h8ymm4w3jhytnpwpczqmt0de6xsmre2pkxzm3qydmkzdjrdev9s7zhgfaqxqyjw5qcqpjrzjqt6xptnd85lpqnu2lefq4cx070v5cdwzh2xlvmdgnu7gqp4zvkus5zapryqqx9qqqyqqqqqqqqqqqcsq9q9qyysgqen77vu8xqjelum24hgjpgfdgfgx4q0nehhalcmuggt32japhjuksq9jv6eksjfnppm4hrzsgyxt8y8xacxut9qv3fpyetz8t7tsymygq8yzn05"
437				},
438				"onchain": {
439					"state": "EXPECT_PAYMENT",
440					"expires_at": "2015-01-25T19:29:44.612Z",
441					"fee_total_sat": "9999",
442					"order_total_sat": "2009999",
443					"address" : "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr",
444					"min_fee_for_0conf": 253,
445					"min_onchain_payment_confirmations": 0,
446					"refund_onchain_address": "bc1qvmsy0f3yyes6z9jvddk8xqwznndmdwapvrc0xrmhd3vqj5rhdrrq6hz49h"
447				}
448			},
449			"channel": null
450		}"#;
451		let _create_order_response: CreateOrderResponse = serde_json::from_str(json_str).unwrap();
452
453		let json_str = r#"{
454			"order_id": "bb4b5d0a-8334-49d8-9463-90a6d413af7c"
455		}"#;
456		let _get_order_request: GetOrderRequest = serde_json::from_str(json_str).unwrap();
457
458		let json_str = r#"{
459			"funded_at": "2012-04-23T18:25:43.511Z",
460			"funding_outpoint": "0301e0480b374b32851a9462db29dc19fe830a7f7d7a88b81612b9d42099c0ae:0",
461			"expires_at": "2012-04-23T18:25:43.511Z"
462		}"#;
463		let _channel: ChannelInfo = serde_json::from_str(json_str).unwrap();
464
465		let json_str = r#""CANCELLED""#;
466		let payment_state: PaymentState = serde_json::from_str(json_str).unwrap();
467		assert_eq!(payment_state, PaymentState::Refunded);
468
469		let json_str = r#""REFUNDED""#;
470		let payment_state: PaymentState = serde_json::from_str(json_str).unwrap();
471		assert_eq!(payment_state, PaymentState::Refunded);
472	}
473}