use lexe_byte_array::ByteArray;
#[cfg(any(test, feature = "test-utils"))]
use lexe_common::test_utils::arbitrary;
use lexe_common::{RefCast, time::TimestampMs};
use lexe_serde::{base64_or_bytes, hexstr_or_bytes};
#[cfg(any(test, feature = "test-utils"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[repr(transparent)]
pub struct NostrEventId(#[serde(with = "hexstr_or_bytes")] pub [u8; 32]);
lexe_byte_array::impl_byte_array!(NostrEventId, 32);
lexe_byte_array::impl_debug_display_as_hex!(NostrEventId);
#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[repr(transparent)]
pub struct NostrPk(#[serde(with = "hexstr_or_bytes")] pub [u8; 32]);
lexe_byte_array::impl_byte_array!(NostrPk, 32);
lexe_byte_array::impl_debug_display_as_hex!(NostrPk);
#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[repr(transparent)]
pub struct NostrSk(#[serde(with = "hexstr_or_bytes")] [u8; 32]);
lexe_byte_array::impl_byte_array!(NostrSk, 32);
lexe_byte_array::impl_debug_display_redacted!(NostrSk);
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NostrPkStruct {
pub nostr_pk: NostrPk,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct DbNwcClientFields {
pub client_nostr_pk: NostrPk,
pub wallet_nostr_pk: NostrPk,
#[serde(with = "base64_or_bytes")]
pub ciphertext: Vec<u8>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct DbNwcClient {
#[serde(flatten)]
pub fields: DbNwcClientFields,
pub created_at: TimestampMs,
pub updated_at: TimestampMs,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NwcClientInfo {
pub client_nostr_pk: NostrPk,
pub wallet_nostr_pk: NostrPk,
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_string()")
)]
pub label: String,
pub created_at: TimestampMs,
pub updated_at: TimestampMs,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct ListNwcClientResponse {
pub clients: Vec<NwcClientInfo>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct CreateNwcClientRequest {
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_string()")
)]
pub label: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct UpdateNwcClientRequest {
pub client_nostr_pk: NostrPk,
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_option_string()")
)]
pub label: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct CreateNwcClientResponse {
pub wallet_nostr_pk: NostrPk,
pub client_nostr_pk: NostrPk,
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_string()")
)]
pub label: String,
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_string()")
)]
pub connection_string: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct UpdateNwcClientResponse {
pub client_info: NwcClientInfo,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct GetNwcClients {
pub client_nostr_pk: Option<NostrPk>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct VecDbNwcClient {
pub nwc_clients: Vec<DbNwcClient>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NwcRequest {
pub client_nostr_pk: NostrPk,
pub wallet_nostr_pk: NostrPk,
pub event_id: NostrEventId,
#[serde(with = "base64_or_bytes")]
pub nip44_payload: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NostrSignedEvent {
#[serde(with = "base64_or_bytes")]
pub event: Vec<u8>,
}
pub mod nip47 {
use std::fmt;
#[cfg(any(test, feature = "test-utils"))]
use lexe_common::test_utils::arbitrary;
#[cfg(any(test, feature = "test-utils"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[serde(rename_all = "snake_case")]
pub enum NwcMethod {
GetInfo,
MakeInvoice,
LookupInvoice,
ListTransactions,
GetBalance,
MultiPayKeysend,
PayKeysend,
MultiPayInvoice,
PayInvoice,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MakeInvoiceParams {
#[serde(rename = "amount")]
pub amount_msat: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description_hash: Option<String>,
#[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
pub expiry_secs: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NwcRequestPayload {
pub method: NwcMethod,
pub params: serde_json::Value,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GetInfoResult {
pub alias: String,
pub color: String,
pub pubkey: String,
pub network: String,
pub block_height: u32,
pub block_hash: String,
pub methods: Vec<NwcMethod>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MakeInvoiceResult {
pub invoice: String,
pub payment_hash: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NwcErrorCode {
RateLimited,
NotImplemented,
InsufficientBalance,
QuotaExceeded,
Restricted,
Unauthorized,
Internal,
Other,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NwcError {
pub code: NwcErrorCode,
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_string()")
)]
pub message: String,
}
impl NwcError {
pub fn new(code: NwcErrorCode, message: String) -> Self {
Self { code, message }
}
pub fn not_implemented(message: impl fmt::Display) -> Self {
let message = format!("Not implemented: {message:#}");
Self {
code: NwcErrorCode::NotImplemented,
message,
}
}
pub fn other(message: impl fmt::Display) -> Self {
let message = format!("Other error: {message:#}");
Self {
code: NwcErrorCode::Other,
message,
}
}
pub fn internal(message: impl fmt::Display) -> Self {
let message = format!("Internal error: {message:#}");
Self {
code: NwcErrorCode::Internal,
message,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NwcResponsePayload {
pub result_type: NwcMethod,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(
strategy = "arbitrary::any_option_json_value_skip_none()"
)
)]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<NwcError>,
}
}
#[cfg(test)]
mod test {
use lexe_common::test_utils::roundtrip;
use super::*;
#[test]
fn db_nwc_client_fields_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<DbNwcClientFields>();
}
#[test]
fn create_nwc_client_request_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<CreateNwcClientRequest>();
}
#[test]
fn update_nwc_client_request_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<UpdateNwcClientRequest>();
}
#[test]
fn create_nwc_client_response_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<CreateNwcClientResponse>();
}
#[test]
fn update_nwc_client_response_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<UpdateNwcClientResponse>();
}
#[test]
fn nwc_client_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<DbNwcClient>();
}
#[test]
fn nostr_client_pk_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<NostrPkStruct>();
}
#[test]
fn vec_nostr_client_pk_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<VecDbNwcClient>();
}
#[test]
fn nwc_request_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<NwcRequest>();
}
#[test]
fn nostr_signed_event_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<NostrSignedEvent>();
}
#[test]
fn nostr_event_id_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<NostrEventId>();
}
#[test]
fn nostr_sk_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<NostrSk>();
}
#[test]
fn nwc_response_payload_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<nip47::NwcResponsePayload>();
}
}