1#[cfg(any(test, feature = "test-utils"))]
2use lexe_common::test_utils::arbitrary;
3use lexe_common::{RefCast, time::TimestampMs};
4use lexe_serde::{base64_or_bytes, hexstr_or_bytes};
5#[cfg(any(test, feature = "test-utils"))]
6use proptest_derive::Arbitrary;
7use serde::{Deserialize, Serialize};
8
9#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
11#[derive(Serialize, Deserialize)]
12#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
13#[repr(transparent)]
14pub struct NostrEventId(#[serde(with = "hexstr_or_bytes")] pub [u8; 32]);
15
16lexe_byte_array::impl_byte_array!(NostrEventId, 32);
17lexe_byte_array::impl_debug_display_as_hex!(NostrEventId);
18
19#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
21#[derive(Serialize, Deserialize)]
22#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
23#[repr(transparent)]
24pub struct NostrPk(#[serde(with = "hexstr_or_bytes")] pub [u8; 32]);
25
26lexe_byte_array::impl_byte_array!(NostrPk, 32);
27lexe_byte_array::impl_debug_display_as_hex!(NostrPk);
28
29#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
31#[derive(Serialize, Deserialize)]
32#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
33#[repr(transparent)]
34pub struct NostrSk(#[serde(with = "hexstr_or_bytes")] [u8; 32]);
35
36lexe_byte_array::impl_byte_array!(NostrSk, 32);
37lexe_byte_array::impl_debug_display_redacted!(NostrSk);
38
39#[derive(Debug, PartialEq, Serialize, Deserialize)]
41#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
42pub struct NostrPkStruct {
43 pub nostr_pk: NostrPk,
44}
45
46#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
48#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
49pub struct DbNwcClientFields {
50 pub client_nostr_pk: NostrPk,
52 pub wallet_nostr_pk: NostrPk,
54 #[serde(with = "base64_or_bytes")]
56 pub ciphertext: Vec<u8>,
57}
58
59#[derive(Debug, PartialEq, Serialize, Deserialize)]
61#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
62pub struct DbNwcClient {
63 #[serde(flatten)]
64 pub fields: DbNwcClientFields,
65 pub created_at: TimestampMs,
66 pub updated_at: TimestampMs,
67}
68
69#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
73#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
74pub struct NwcClientInfo {
75 pub client_nostr_pk: NostrPk,
77 pub wallet_nostr_pk: NostrPk,
79 #[cfg_attr(
81 any(test, feature = "test-utils"),
82 proptest(strategy = "arbitrary::any_string()")
83 )]
84 pub label: String,
85 pub created_at: TimestampMs,
87 pub updated_at: TimestampMs,
89}
90
91#[derive(Debug, PartialEq, Serialize, Deserialize)]
95#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
96pub struct ListNwcClientResponse {
97 pub clients: Vec<NwcClientInfo>,
98}
99
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
104#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
105pub struct CreateNwcClientRequest {
106 #[cfg_attr(
108 any(test, feature = "test-utils"),
109 proptest(strategy = "arbitrary::any_string()")
110 )]
111 pub label: String,
112}
113
114#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
118#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
119pub struct UpdateNwcClientRequest {
120 pub client_nostr_pk: NostrPk,
122 #[cfg_attr(
125 any(test, feature = "test-utils"),
126 proptest(strategy = "arbitrary::any_option_string()")
127 )]
128 pub label: Option<String>,
129}
130
131#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
133#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
134pub struct CreateNwcClientResponse {
135 pub wallet_nostr_pk: NostrPk,
137 pub client_nostr_pk: NostrPk,
139 #[cfg_attr(
141 any(test, feature = "test-utils"),
142 proptest(strategy = "arbitrary::any_string()")
143 )]
144 pub label: String,
145 #[cfg_attr(
147 any(test, feature = "test-utils"),
148 proptest(strategy = "arbitrary::any_string()")
149 )]
150 pub connection_string: String,
151}
152
153#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
157#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
158pub struct UpdateNwcClientResponse {
159 pub client_info: NwcClientInfo,
161}
162
163#[derive(Debug, PartialEq, Serialize, Deserialize)]
169#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
170pub struct GetNwcClients {
171 pub client_nostr_pk: Option<NostrPk>,
173}
174
175#[derive(Debug, PartialEq, Serialize, Deserialize)]
176#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
177pub struct VecDbNwcClient {
178 pub nwc_clients: Vec<DbNwcClient>,
179}
180
181#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
185#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
186pub struct NwcRequest {
187 pub client_nostr_pk: NostrPk,
189 pub wallet_nostr_pk: NostrPk,
191 pub event_id: NostrEventId,
193 #[serde(with = "base64_or_bytes")]
195 pub nip44_payload: Vec<u8>,
196}
197
198#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
202#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
203pub struct NostrSignedEvent {
204 #[serde(with = "base64_or_bytes")]
206 pub event: Vec<u8>,
207}
208
209pub mod nip47 {
211 use std::fmt;
212
213 #[cfg(any(test, feature = "test-utils"))]
214 use lexe_common::test_utils::arbitrary;
215 #[cfg(any(test, feature = "test-utils"))]
216 use proptest_derive::Arbitrary;
217 use serde::{Deserialize, Serialize};
218
219 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
221 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
222 #[serde(rename_all = "snake_case")]
223 pub enum NwcMethod {
224 GetInfo,
225 MakeInvoice,
226 LookupInvoice,
227 ListTransactions,
228 GetBalance,
229 MultiPayKeysend,
230 PayKeysend,
231 MultiPayInvoice,
232 PayInvoice,
233 }
234
235 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
237 pub struct MakeInvoiceParams {
238 #[serde(rename = "amount")]
240 pub amount_msat: u64,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub description: Option<String>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub description_hash: Option<String>,
247 #[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
249 pub expiry_secs: Option<u32>,
250 #[serde(skip_serializing_if = "Option::is_none")]
253 pub metadata: Option<serde_json::Value>,
254 }
255
256 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
258 pub struct NwcRequestPayload {
259 pub method: NwcMethod,
260 pub params: serde_json::Value,
261 }
262
263 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
265 pub struct GetInfoResult {
266 pub alias: String,
268 pub color: String,
270 pub pubkey: String,
272 pub network: String,
274 pub block_height: u32,
276 pub block_hash: String,
278 pub methods: Vec<NwcMethod>,
280 }
281
282 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
284 pub struct MakeInvoiceResult {
285 pub invoice: String,
287 pub payment_hash: String,
289 }
290
291 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
293 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
294 #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
295 pub enum NwcErrorCode {
296 RateLimited,
297 NotImplemented,
298 InsufficientBalance,
299 QuotaExceeded,
300 Restricted,
301 Unauthorized,
302 Internal,
303 Other,
304 }
305
306 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
308 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
309 pub struct NwcError {
310 pub code: NwcErrorCode,
311 #[cfg_attr(
312 any(test, feature = "test-utils"),
313 proptest(strategy = "arbitrary::any_string()")
314 )]
315 pub message: String,
316 }
317
318 impl NwcError {
319 pub fn new(code: NwcErrorCode, message: String) -> Self {
320 Self { code, message }
321 }
322
323 pub fn not_implemented(message: impl fmt::Display) -> Self {
324 let message = format!("Not implemented: {message:#}");
325 Self {
326 code: NwcErrorCode::NotImplemented,
327 message,
328 }
329 }
330
331 pub fn other(message: impl fmt::Display) -> Self {
332 let message = format!("Other error: {message:#}");
333 Self {
334 code: NwcErrorCode::Other,
335 message,
336 }
337 }
338
339 pub fn internal(message: impl fmt::Display) -> Self {
340 let message = format!("Internal error: {message:#}");
341 Self {
342 code: NwcErrorCode::Internal,
343 message,
344 }
345 }
346 }
347
348 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
350 #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
351 pub struct NwcResponsePayload {
352 pub result_type: NwcMethod,
353 #[serde(skip_serializing_if = "Option::is_none")]
354 #[cfg_attr(
355 any(test, feature = "test-utils"),
356 proptest(
357 strategy = "arbitrary::any_option_json_value_skip_none()"
358 )
359 )]
360 pub result: Option<serde_json::Value>,
361 #[serde(skip_serializing_if = "Option::is_none")]
362 pub error: Option<NwcError>,
363 }
364}
365
366#[cfg(test)]
367mod test {
368 use lexe_common::test_utils::roundtrip;
369
370 use super::*;
371
372 #[test]
373 fn db_nwc_client_fields_roundtrip() {
374 roundtrip::json_value_roundtrip_proptest::<DbNwcClientFields>();
375 }
376
377 #[test]
378 fn create_nwc_client_request_roundtrip() {
379 roundtrip::json_value_roundtrip_proptest::<CreateNwcClientRequest>();
380 }
381
382 #[test]
383 fn update_nwc_client_request_roundtrip() {
384 roundtrip::json_value_roundtrip_proptest::<UpdateNwcClientRequest>();
385 }
386
387 #[test]
388 fn create_nwc_client_response_roundtrip() {
389 roundtrip::json_value_roundtrip_proptest::<CreateNwcClientResponse>();
390 }
391
392 #[test]
393 fn update_nwc_client_response_roundtrip() {
394 roundtrip::json_value_roundtrip_proptest::<UpdateNwcClientResponse>();
395 }
396
397 #[test]
398 fn nwc_client_roundtrip() {
399 roundtrip::json_value_roundtrip_proptest::<DbNwcClient>();
400 }
401
402 #[test]
403 fn nostr_client_pk_roundtrip() {
404 roundtrip::json_value_roundtrip_proptest::<NostrPkStruct>();
405 }
406
407 #[test]
408 fn vec_nostr_client_pk_roundtrip() {
409 roundtrip::json_value_roundtrip_proptest::<VecDbNwcClient>();
410 }
411
412 #[test]
413 fn nwc_request_roundtrip() {
414 roundtrip::json_value_roundtrip_proptest::<NwcRequest>();
415 }
416
417 #[test]
418 fn nostr_signed_event_roundtrip() {
419 roundtrip::json_value_roundtrip_proptest::<NostrSignedEvent>();
420 }
421
422 #[test]
423 fn nostr_event_id_roundtrip() {
424 roundtrip::json_value_roundtrip_proptest::<NostrEventId>();
425 }
426
427 #[test]
428 fn nostr_sk_roundtrip() {
429 roundtrip::json_value_roundtrip_proptest::<NostrSk>();
430 }
431
432 #[test]
433 fn nwc_response_payload_roundtrip() {
434 roundtrip::json_value_roundtrip_proptest::<nip47::NwcResponsePayload>();
435 }
436}