1use lexe_byte_array::ByteArray;
2#[cfg(any(test, feature = "test-utils"))]
3use lexe_common::test_utils::arbitrary;
4use lexe_common::{RefCast, time::TimestampMs};
5use lexe_serde::{base64_or_bytes, hexstr_or_bytes};
6#[cfg(any(test, feature = "test-utils"))]
7use proptest_derive::Arbitrary;
8use serde::{Deserialize, Serialize};
9
10#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
12#[derive(Serialize, Deserialize)]
13#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
14#[repr(transparent)]
15pub struct NostrEventId(#[serde(with = "hexstr_or_bytes")] pub [u8; 32]);
16
17lexe_byte_array::impl_byte_array!(NostrEventId, 32);
18lexe_byte_array::impl_debug_display_as_hex!(NostrEventId);
19
20#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
22#[derive(Serialize, Deserialize)]
23#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
24#[repr(transparent)]
25pub struct NostrPk(#[serde(with = "hexstr_or_bytes")] pub [u8; 32]);
26
27lexe_byte_array::impl_byte_array!(NostrPk, 32);
28lexe_byte_array::impl_debug_display_as_hex!(NostrPk);
29
30#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
32#[derive(Serialize, Deserialize)]
33#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
34#[repr(transparent)]
35pub struct NostrSk(#[serde(with = "hexstr_or_bytes")] [u8; 32]);
36
37lexe_byte_array::impl_byte_array!(NostrSk, 32);
38lexe_byte_array::impl_debug_display_redacted!(NostrSk);
39
40#[derive(Debug, PartialEq, Serialize, Deserialize)]
42#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
43pub struct NostrPkStruct {
44 pub nostr_pk: NostrPk,
45}
46
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
50pub struct DbNwcClientFields {
51 pub client_nostr_pk: NostrPk,
53 pub wallet_nostr_pk: NostrPk,
55 #[serde(with = "base64_or_bytes")]
57 pub ciphertext: Vec<u8>,
58}
59
60#[derive(Debug, PartialEq, Serialize, Deserialize)]
62#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
63pub struct DbNwcClient {
64 #[serde(flatten)]
65 pub fields: DbNwcClientFields,
66 pub created_at: TimestampMs,
67 pub updated_at: TimestampMs,
68}
69
70#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
74#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
75pub struct NwcClientInfo {
76 pub client_nostr_pk: NostrPk,
78 pub wallet_nostr_pk: NostrPk,
80 #[cfg_attr(
82 any(test, feature = "test-utils"),
83 proptest(strategy = "arbitrary::any_string()")
84 )]
85 pub label: String,
86 pub created_at: TimestampMs,
88 pub updated_at: TimestampMs,
90}
91
92#[derive(Debug, PartialEq, Serialize, Deserialize)]
96#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
97pub struct ListNwcClientResponse {
98 pub clients: Vec<NwcClientInfo>,
99}
100
101#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
105#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
106pub struct CreateNwcClientRequest {
107 #[cfg_attr(
109 any(test, feature = "test-utils"),
110 proptest(strategy = "arbitrary::any_string()")
111 )]
112 pub label: String,
113}
114
115#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
119#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
120pub struct UpdateNwcClientRequest {
121 pub client_nostr_pk: NostrPk,
123 #[cfg_attr(
126 any(test, feature = "test-utils"),
127 proptest(strategy = "arbitrary::any_option_string()")
128 )]
129 pub label: Option<String>,
130}
131
132#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
134#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
135pub struct CreateNwcClientResponse {
136 pub wallet_nostr_pk: NostrPk,
138 pub client_nostr_pk: NostrPk,
140 #[cfg_attr(
142 any(test, feature = "test-utils"),
143 proptest(strategy = "arbitrary::any_string()")
144 )]
145 pub label: String,
146 #[cfg_attr(
148 any(test, feature = "test-utils"),
149 proptest(strategy = "arbitrary::any_string()")
150 )]
151 pub connection_string: String,
152}
153
154#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
158#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
159pub struct UpdateNwcClientResponse {
160 pub client_info: NwcClientInfo,
162}
163
164#[derive(Debug, PartialEq, Serialize, Deserialize)]
170#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
171pub struct GetNwcClients {
172 pub client_nostr_pk: Option<NostrPk>,
174}
175
176#[derive(Debug, PartialEq, Serialize, Deserialize)]
177#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
178pub struct VecDbNwcClient {
179 pub nwc_clients: Vec<DbNwcClient>,
180}
181
182#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
186#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
187pub struct NwcRequest {
188 pub client_nostr_pk: NostrPk,
190 pub wallet_nostr_pk: NostrPk,
192 pub event_id: NostrEventId,
194 #[serde(with = "base64_or_bytes")]
196 pub nip44_payload: Vec<u8>,
197}
198
199#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
203#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
204pub struct NostrSignedEvent {
205 #[serde(with = "base64_or_bytes")]
207 pub event: Vec<u8>,
208}
209
210pub mod nip47 {
212 use std::fmt;
213
214 #[cfg(any(test, feature = "test-utils"))]
215 use lexe_common::test_utils::arbitrary;
216 #[cfg(any(test, feature = "test-utils"))]
217 use proptest_derive::Arbitrary;
218 use serde::{Deserialize, Serialize};
219
220 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
222 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
223 #[serde(rename_all = "snake_case")]
224 pub enum NwcMethod {
225 GetInfo,
226 MakeInvoice,
227 LookupInvoice,
228 ListTransactions,
229 GetBalance,
230 MultiPayKeysend,
231 PayKeysend,
232 MultiPayInvoice,
233 PayInvoice,
234 }
235
236 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
238 pub struct MakeInvoiceParams {
239 #[serde(rename = "amount")]
241 pub amount_msat: u64,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub description: Option<String>,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub description_hash: Option<String>,
248 #[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
250 pub expiry_secs: Option<u32>,
251 #[serde(skip_serializing_if = "Option::is_none")]
254 pub metadata: Option<serde_json::Value>,
255 }
256
257 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
259 pub struct NwcRequestPayload {
260 pub method: NwcMethod,
261 pub params: serde_json::Value,
262 }
263
264 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
266 pub struct GetInfoResult {
267 pub alias: String,
269 pub color: String,
271 pub pubkey: String,
273 pub network: String,
275 pub block_height: u32,
277 pub block_hash: String,
279 pub methods: Vec<NwcMethod>,
281 }
282
283 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
285 pub struct MakeInvoiceResult {
286 pub invoice: String,
288 pub payment_hash: String,
290 }
291
292 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
294 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
295 #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
296 pub enum NwcErrorCode {
297 RateLimited,
298 NotImplemented,
299 InsufficientBalance,
300 QuotaExceeded,
301 Restricted,
302 Unauthorized,
303 Internal,
304 Other,
305 }
306
307 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
309 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
310 pub struct NwcError {
311 pub code: NwcErrorCode,
312 #[cfg_attr(
313 any(test, feature = "test-utils"),
314 proptest(strategy = "arbitrary::any_string()")
315 )]
316 pub message: String,
317 }
318
319 impl NwcError {
320 pub fn new(code: NwcErrorCode, message: String) -> Self {
321 Self { code, message }
322 }
323
324 pub fn not_implemented(message: impl fmt::Display) -> Self {
325 let message = format!("Not implemented: {message:#}");
326 Self {
327 code: NwcErrorCode::NotImplemented,
328 message,
329 }
330 }
331
332 pub fn other(message: impl fmt::Display) -> Self {
333 let message = format!("Other error: {message:#}");
334 Self {
335 code: NwcErrorCode::Other,
336 message,
337 }
338 }
339
340 pub fn internal(message: impl fmt::Display) -> Self {
341 let message = format!("Internal error: {message:#}");
342 Self {
343 code: NwcErrorCode::Internal,
344 message,
345 }
346 }
347 }
348
349 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
351 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
352 pub struct NwcResponsePayload {
353 pub result_type: NwcMethod,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 #[cfg_attr(
356 any(test, feature = "test-utils"),
357 proptest(
358 strategy = "arbitrary::any_option_json_value_skip_none()"
359 )
360 )]
361 pub result: Option<serde_json::Value>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub error: Option<NwcError>,
364 }
365}
366
367#[cfg(test)]
368mod test {
369 use lexe_common::test_utils::roundtrip;
370
371 use super::*;
372
373 #[test]
374 fn db_nwc_client_fields_roundtrip() {
375 roundtrip::json_value_roundtrip_proptest::<DbNwcClientFields>();
376 }
377
378 #[test]
379 fn create_nwc_client_request_roundtrip() {
380 roundtrip::json_value_roundtrip_proptest::<CreateNwcClientRequest>();
381 }
382
383 #[test]
384 fn update_nwc_client_request_roundtrip() {
385 roundtrip::json_value_roundtrip_proptest::<UpdateNwcClientRequest>();
386 }
387
388 #[test]
389 fn create_nwc_client_response_roundtrip() {
390 roundtrip::json_value_roundtrip_proptest::<CreateNwcClientResponse>();
391 }
392
393 #[test]
394 fn update_nwc_client_response_roundtrip() {
395 roundtrip::json_value_roundtrip_proptest::<UpdateNwcClientResponse>();
396 }
397
398 #[test]
399 fn nwc_client_roundtrip() {
400 roundtrip::json_value_roundtrip_proptest::<DbNwcClient>();
401 }
402
403 #[test]
404 fn nostr_client_pk_roundtrip() {
405 roundtrip::json_value_roundtrip_proptest::<NostrPkStruct>();
406 }
407
408 #[test]
409 fn vec_nostr_client_pk_roundtrip() {
410 roundtrip::json_value_roundtrip_proptest::<VecDbNwcClient>();
411 }
412
413 #[test]
414 fn nwc_request_roundtrip() {
415 roundtrip::json_value_roundtrip_proptest::<NwcRequest>();
416 }
417
418 #[test]
419 fn nostr_signed_event_roundtrip() {
420 roundtrip::json_value_roundtrip_proptest::<NostrSignedEvent>();
421 }
422
423 #[test]
424 fn nostr_event_id_roundtrip() {
425 roundtrip::json_value_roundtrip_proptest::<NostrEventId>();
426 }
427
428 #[test]
429 fn nostr_sk_roundtrip() {
430 roundtrip::json_value_roundtrip_proptest::<NostrSk>();
431 }
432
433 #[test]
434 fn nwc_response_payload_roundtrip() {
435 roundtrip::json_value_roundtrip_proptest::<nip47::NwcResponsePayload>();
436 }
437}