1use 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#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)]
30pub struct OrderId(pub String);
31
32#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
37#[serde(default)]
38pub struct GetInfoRequest {}
39
40#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
42pub struct LSPS1Options {
43 pub min_required_channel_confirmations: u16,
45 pub min_funding_confirms_within_blocks: u16,
47 pub supports_zero_channel_reserve: bool,
49 pub max_channel_expiry_blocks: u32,
51 #[serde(with = "string_amount")]
53 pub min_initial_client_balance_sat: u64,
54 #[serde(with = "string_amount")]
56 pub max_initial_client_balance_sat: u64,
57 #[serde(with = "string_amount")]
59 pub min_initial_lsp_balance_sat: u64,
60 #[serde(with = "string_amount")]
62 pub max_initial_lsp_balance_sat: u64,
63 #[serde(with = "string_amount")]
65 pub min_channel_balance_sat: u64,
66 #[serde(with = "string_amount")]
68 pub max_channel_balance_sat: u64,
69}
70
71#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
73pub struct GetInfoResponse {
74 #[serde(flatten)]
76 pub options: LSPS1Options,
77}
78
79#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
84pub struct CreateOrderRequest {
85 #[serde(flatten)]
87 pub order: OrderParameters,
88 #[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#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
97pub struct OrderParameters {
98 #[serde(with = "string_amount")]
100 pub lsp_balance_sat: u64,
101 #[serde(with = "string_amount")]
106 pub client_balance_sat: u64,
107 pub required_channel_confirmations: u16,
109 pub funding_confirms_within_blocks: u16,
111 pub channel_expiry_blocks: u32,
113 pub token: Option<String>,
115 pub announce_channel: bool,
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
121pub struct CreateOrderResponse {
122 pub order_id: OrderId,
124 #[serde(flatten)]
126 pub order: OrderParameters,
127 pub created_at: chrono::DateTime<Utc>,
129 pub order_state: OrderState,
131 pub payment: PaymentInfo,
133 pub channel: Option<ChannelInfo>,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
139#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
140pub enum OrderState {
141 Created,
143 Completed,
145 Failed,
147}
148
149#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
151pub struct PaymentInfo {
152 pub bolt11: Option<Bolt11PaymentInfo>,
154 pub onchain: Option<OnchainPaymentInfo>,
156}
157
158#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
160pub struct Bolt11PaymentInfo {
161 pub state: PaymentState,
163 pub expires_at: chrono::DateTime<Utc>,
165 #[serde(with = "string_amount")]
167 pub fee_total_sat: u64,
168 #[serde(with = "string_amount")]
170 pub order_total_sat: u64,
171 pub invoice: Bolt11Invoice,
173}
174
175#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
177pub struct OnchainPaymentInfo {
178 pub state: PaymentState,
180 pub expires_at: chrono::DateTime<Utc>,
182 #[serde(with = "string_amount")]
184 pub fee_total_sat: u64,
185 #[serde(with = "string_amount")]
187 pub order_total_sat: u64,
188 #[serde(with = "unchecked_address")]
191 pub address: Address,
192 pub min_onchain_payment_confirmations: Option<u16>,
195 #[serde(with = "u32_fee_rate")]
198 pub min_fee_for_0conf: FeeRate,
199 #[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#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
211#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
212pub enum PaymentState {
213 ExpectPayment,
215 Paid,
217 #[serde(alias = "CANCELLED")]
219 Refunded,
220}
221
222#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
224pub struct OnchainPayment {
225 pub outpoint: String,
227 #[serde(with = "string_amount")]
229 pub sat: u64,
230 pub confirmed: bool,
232}
233
234#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
236pub struct ChannelInfo {
237 pub funded_at: chrono::DateTime<Utc>,
239 pub funding_outpoint: OutPoint,
241 pub expires_at: chrono::DateTime<Utc>,
243}
244
245#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
250pub struct GetOrderRequest {
251 pub order_id: OrderId,
253}
254
255#[derive(Clone, Debug, PartialEq, Eq)]
257pub enum LSPS1Request {
258 GetInfo(GetInfoRequest),
260 CreateOrder(CreateOrderRequest),
262 GetOrder(GetOrderRequest),
264}
265
266#[derive(Clone, Debug, PartialEq, Eq)]
268pub enum LSPS1Response {
269 GetInfo(GetInfoResponse),
271 GetInfoError(ResponseError),
273 CreateOrder(CreateOrderResponse),
275 CreateOrderError(ResponseError),
277 GetOrder(CreateOrderResponse),
279 GetOrderError(ResponseError),
281}
282
283#[derive(Clone, Debug, PartialEq, Eq)]
285pub enum LSPS1Message {
286 Request(RequestId, LSPS1Request),
288 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 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}