use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::convert::Infallible;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::str::FromStr;
use serde::ser::{SerializeMap, SerializeStruct};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use super::nip04;
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::event::FinalizeEvent;
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::signer::SignerError;
use crate::types::url::form_urlencoded::byte_serialize;
use crate::types::url::{RelayUrl, Url};
use crate::{Event, JsonUtil, PublicKey, SecretKey, Timestamp};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::{EventBuilder, Keys, Kind, Tag};
#[derive(Debug)]
pub enum Error {
Json(serde_json::Error),
NIP04(nip04::Error),
#[cfg(all(feature = "std", feature = "os-rng"))]
Signer(SignerError),
ErrorCode(NIP47Error),
CantDeserializeResponse {
response: String,
error: String,
},
UnsupportedMethod(Method),
UnexpectedResult,
InvalidURI,
}
impl core::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Json(e) => e.fmt(f),
Self::NIP04(e) => e.fmt(f),
#[cfg(all(feature = "std", feature = "os-rng"))]
Self::Signer(e) => e.fmt(f),
Self::ErrorCode(e) => e.fmt(f),
Self::CantDeserializeResponse { response, error } => write!(
f,
"Can't deserialize response: response={response}, error={error}"
),
Self::UnsupportedMethod(name) => write!(f, "Unsupported method: {name}"),
Self::UnexpectedResult => f.write_str("Unexpected result"),
Self::InvalidURI => f.write_str("Invalid URI"),
}
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Self::Json(e)
}
}
impl From<nip04::Error> for Error {
fn from(e: nip04::Error) -> Self {
Self::NIP04(e)
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
impl From<SignerError> for Error {
fn from(e: SignerError) -> Self {
Self::Signer(e)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ErrorCode {
#[serde(rename = "RATE_LIMITED")]
RateLimited,
#[serde(rename = "NOT_IMPLEMENTED")]
NotImplemented,
#[serde(rename = "INSUFFICIENT_BALANCE")]
InsufficientBalance,
#[serde(rename = "PAYMENT_FAILED")]
PaymentFailed,
#[serde(rename = "NOT_FOUND")]
NotFound,
#[serde(rename = "QUOTA_EXCEEDED")]
QuotaExceeded,
#[serde(rename = "RESTRICTED")]
Restricted,
#[serde(rename = "UNAUTHORIZED")]
Unauthorized,
#[serde(rename = "INTERNAL")]
Internal,
#[serde(rename = "OTHER")]
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct NIP47Error {
pub code: ErrorCode,
pub message: String,
}
impl fmt::Display for NIP47Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} [{:?}]", self.message, self.code)
}
}
#[derive(Debug, Clone)]
pub enum Method {
PayInvoice,
PayKeysend,
MakeInvoice,
LookupInvoice,
ListTransactions,
GetBalance,
GetInfo,
MakeHoldInvoice,
CancelHoldInvoice,
SettleHoldInvoice,
Unknown(String),
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl PartialEq for Method {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Method {}
impl PartialOrd for Method {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Method {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl Hash for Method {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl Method {
pub fn as_str(&self) -> &str {
match self {
Self::PayInvoice => "pay_invoice",
Self::PayKeysend => "pay_keysend",
Self::MakeInvoice => "make_invoice",
Self::LookupInvoice => "lookup_invoice",
Self::ListTransactions => "list_transactions",
Self::GetBalance => "get_balance",
Self::GetInfo => "get_info",
Self::MakeHoldInvoice => "make_hold_invoice",
Self::CancelHoldInvoice => "cancel_hold_invoice",
Self::SettleHoldInvoice => "settle_hold_invoice",
Self::Unknown(method) => method.as_str(),
}
}
}
impl FromStr for Method {
type Err = Infallible;
fn from_str(method: &str) -> Result<Self, Self::Err> {
match method {
"pay_invoice" => Ok(Self::PayInvoice),
"pay_keysend" => Ok(Self::PayKeysend),
"make_invoice" => Ok(Self::MakeInvoice),
"lookup_invoice" => Ok(Self::LookupInvoice),
"list_transactions" => Ok(Self::ListTransactions),
"get_balance" => Ok(Self::GetBalance),
"get_info" => Ok(Self::GetInfo),
"make_hold_invoice" => Ok(Self::MakeHoldInvoice),
"cancel_hold_invoice" => Ok(Self::CancelHoldInvoice),
"settle_hold_invoice" => Ok(Self::SettleHoldInvoice),
m => Ok(Self::Unknown(m.to_string())),
}
}
}
impl Serialize for Method {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Method {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let method: String = Deserialize::deserialize(deserializer)?;
Self::from_str(&method).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RequestParams {
PayInvoice(PayInvoiceRequest),
PayKeysend(PayKeysendRequest),
MakeInvoice(MakeInvoiceRequest),
LookupInvoice(LookupInvoiceRequest),
ListTransactions(ListTransactionsRequest),
GetBalance,
GetInfo,
MakeHoldInvoice(MakeHoldInvoiceRequest),
CancelHoldInvoice(CancelHoldInvoiceRequest),
SettleHoldInvoice(SettleHoldInvoiceRequest),
}
impl Serialize for RequestParams {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
RequestParams::PayInvoice(p) => p.serialize(serializer),
RequestParams::PayKeysend(p) => p.serialize(serializer),
RequestParams::MakeInvoice(p) => p.serialize(serializer),
RequestParams::LookupInvoice(p) => p.serialize(serializer),
RequestParams::ListTransactions(p) => p.serialize(serializer),
RequestParams::GetBalance => {
let map = serializer.serialize_map(None)?;
map.end()
}
RequestParams::GetInfo => {
let map = serializer.serialize_map(None)?;
map.end()
}
RequestParams::MakeHoldInvoice(p) => p.serialize(serializer),
RequestParams::CancelHoldInvoice(p) => p.serialize(serializer),
RequestParams::SettleHoldInvoice(p) => p.serialize(serializer),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PayInvoiceRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub invoice: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<u64>,
}
impl PayInvoiceRequest {
#[inline]
pub fn new<S>(invoice: S) -> Self
where
S: Into<String>,
{
Self {
id: None,
invoice: invoice.into(),
amount: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct KeysendTLVRecord {
#[serde(rename = "type")]
pub tlv_type: u64,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PayKeysendRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub amount: u64,
pub pubkey: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub preimage: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tlv_records: Vec<KeysendTLVRecord>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MakeInvoiceRequest {
pub amount: 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(skip_serializing_if = "Option::is_none")]
pub expiry: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LookupInvoiceRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub invoice: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TransactionType {
#[serde(rename = "incoming")]
Incoming,
#[serde(rename = "outgoing")]
Outgoing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum TransactionState {
#[serde(rename = "pending")]
Pending,
#[serde(rename = "settled")]
Settled,
#[serde(rename = "expired")]
Expired,
#[serde(rename = "failed")]
Failed,
#[serde(rename = "accepted")]
Accepted,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ListTransactionsRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub until: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unpaid: Option<bool>,
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<TransactionType>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MakeHoldInvoiceRequest {
pub amount: 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(skip_serializing_if = "Option::is_none")]
pub expiry: Option<u64>,
pub payment_hash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_cltv_expiry_delta: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CancelHoldInvoiceRequest {
pub payment_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SettleHoldInvoiceRequest {
pub preimage: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct Request {
pub method: Method,
pub params: RequestParams,
}
#[derive(Serialize, Deserialize)]
struct RequestTemplate {
method: Method,
#[serde(default)] params: Value,
}
impl Request {
#[inline]
pub fn pay_invoice(params: PayInvoiceRequest) -> Self {
Self {
method: Method::PayInvoice,
params: RequestParams::PayInvoice(params),
}
}
#[inline]
pub fn pay_keysend(params: PayKeysendRequest) -> Self {
Self {
method: Method::PayKeysend,
params: RequestParams::PayKeysend(params),
}
}
#[inline]
pub fn make_invoice(params: MakeInvoiceRequest) -> Self {
Self {
method: Method::MakeInvoice,
params: RequestParams::MakeInvoice(params),
}
}
#[inline]
pub fn lookup_invoice(params: LookupInvoiceRequest) -> Self {
Self {
method: Method::LookupInvoice,
params: RequestParams::LookupInvoice(params),
}
}
#[inline]
pub fn list_transactions(params: ListTransactionsRequest) -> Self {
Self {
method: Method::ListTransactions,
params: RequestParams::ListTransactions(params),
}
}
#[inline]
pub fn get_balance() -> Self {
Self {
method: Method::GetBalance,
params: RequestParams::GetBalance,
}
}
#[inline]
pub fn get_info() -> Self {
Self {
method: Method::GetInfo,
params: RequestParams::GetInfo,
}
}
pub fn from_value(value: Value) -> Result<Self, Error> {
let template: RequestTemplate = serde_json::from_value(value)?;
let params = match template.method {
Method::PayInvoice => {
let params: PayInvoiceRequest = serde_json::from_value(template.params)?;
RequestParams::PayInvoice(params)
}
Method::PayKeysend => {
let params: PayKeysendRequest = serde_json::from_value(template.params)?;
RequestParams::PayKeysend(params)
}
Method::MakeInvoice => {
let params: MakeInvoiceRequest = serde_json::from_value(template.params)?;
RequestParams::MakeInvoice(params)
}
Method::LookupInvoice => {
let params: LookupInvoiceRequest = serde_json::from_value(template.params)?;
RequestParams::LookupInvoice(params)
}
Method::ListTransactions => {
let params: ListTransactionsRequest = serde_json::from_value(template.params)?;
RequestParams::ListTransactions(params)
}
Method::GetBalance => RequestParams::GetBalance,
Method::GetInfo => RequestParams::GetInfo,
Method::MakeHoldInvoice => {
let params: MakeHoldInvoiceRequest = serde_json::from_value(template.params)?;
RequestParams::MakeHoldInvoice(params)
}
Method::SettleHoldInvoice => {
let params: SettleHoldInvoiceRequest = serde_json::from_value(template.params)?;
RequestParams::SettleHoldInvoice(params)
}
Method::CancelHoldInvoice => {
let params: CancelHoldInvoiceRequest = serde_json::from_value(template.params)?;
RequestParams::CancelHoldInvoice(params)
}
Method::Unknown(name) => {
return Err(Error::UnsupportedMethod(Method::Unknown(name)));
}
};
Ok(Self {
method: template.method,
params,
})
}
#[cfg(all(feature = "std", feature = "os-rng"))]
pub fn to_event(self, uri: &NostrWalletConnectUri) -> Result<Event, Error> {
let encrypted = nip04::encrypt(&uri.secret, &uri.public_key, self.as_json())?;
let keys: Keys = Keys::new(uri.secret.clone());
Ok(EventBuilder::new(Kind::WalletConnectRequest, encrypted)
.tag(Tag::public_key(uri.public_key))
.finalize(&keys)?)
}
}
impl JsonUtil for Request {
type Err = Error;
}
impl<'de> Deserialize<'de> for Request {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
Self::from_value(value).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct PayInvoiceResponse {
pub preimage: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub fees_paid: Option<u64>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct PayKeysendResponse {
pub preimage: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub fees_paid: Option<u64>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct MakeInvoiceResponse {
pub invoice: String,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub payment_hash: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description_hash: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub preimage: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<Timestamp>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct LookupInvoiceResponse {
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<TransactionType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<TransactionState>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub invoice: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description_hash: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub preimage: Option<String>,
pub payment_hash: String,
pub amount: u64,
pub fees_paid: u64,
pub created_at: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub settled_at: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct GetBalanceResponse {
pub balance: u64,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct GetInfoResponse {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub alias: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub color: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub pubkey: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub network: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_height: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub block_hash: Option<String>,
pub methods: Vec<Method>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub notifications: Vec<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct MakeHoldInvoiceResponse {
#[serde(rename = "type")]
pub transaction_type: TransactionType,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub invoice: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description_hash: Option<String>,
pub payment_hash: String,
pub amount: u64,
pub created_at: Timestamp,
pub expires_at: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct CancelHoldInvoiceResponse {}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct SettleHoldInvoiceResponse {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseResult {
PayInvoice(PayInvoiceResponse),
PayKeysend(PayKeysendResponse),
MakeInvoice(MakeInvoiceResponse),
LookupInvoice(LookupInvoiceResponse),
ListTransactions(Vec<LookupInvoiceResponse>),
GetBalance(GetBalanceResponse),
GetInfo(GetInfoResponse),
MakeHoldInvoice(MakeHoldInvoiceResponse),
CancelHoldInvoice(CancelHoldInvoiceResponse),
SettleHoldInvoice(SettleHoldInvoiceResponse),
}
impl Serialize for ResponseResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ResponseResult::PayInvoice(p) => p.serialize(serializer),
ResponseResult::PayKeysend(p) => p.serialize(serializer),
ResponseResult::MakeInvoice(p) => p.serialize(serializer),
ResponseResult::LookupInvoice(p) => p.serialize(serializer),
ResponseResult::ListTransactions(p) => {
let mut state = serializer.serialize_struct("ListTransactions", 1)?;
state.serialize_field("transactions", p)?;
state.end()
}
ResponseResult::GetBalance(p) => p.serialize(serializer),
ResponseResult::GetInfo(p) => p.serialize(serializer),
ResponseResult::MakeHoldInvoice(p) => p.serialize(serializer),
ResponseResult::CancelHoldInvoice(p) => p.serialize(serializer),
ResponseResult::SettleHoldInvoice(p) => p.serialize(serializer),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Response {
pub result_type: Method,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<NIP47Error>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<ResponseResult>,
}
#[derive(Debug, Clone, Deserialize)]
struct ResponseTemplate {
pub result_type: Method,
pub error: Option<NIP47Error>,
pub result: Option<Value>,
}
impl Response {
#[inline]
pub fn from_event(uri: &NostrWalletConnectUri, event: &Event) -> Result<Self, Error> {
let decrypt_res: String = nip04::decrypt(&uri.secret, &event.pubkey, &event.content)?;
Self::from_json(&decrypt_res).map_err(|e| Error::CantDeserializeResponse {
response: decrypt_res,
error: e.to_string(),
})
}
pub fn from_value(value: Value) -> Result<Self, Error> {
let template: ResponseTemplate = serde_json::from_value(value)?;
if let Some(result) = template.result {
let result = match template.result_type {
Method::PayInvoice => {
let result: PayInvoiceResponse = serde_json::from_value(result)?;
ResponseResult::PayInvoice(result)
}
Method::PayKeysend => {
let result: PayKeysendResponse = serde_json::from_value(result)?;
ResponseResult::PayKeysend(result)
}
Method::MakeInvoice => {
let result: MakeInvoiceResponse = serde_json::from_value(result)?;
ResponseResult::MakeInvoice(result)
}
Method::LookupInvoice => {
let result: LookupInvoiceResponse = serde_json::from_value(result)?;
ResponseResult::LookupInvoice(result)
}
Method::ListTransactions => {
let transactions: Value = result
.get("transactions")
.cloned()
.ok_or(Error::UnexpectedResult)?;
let result: Vec<LookupInvoiceResponse> = serde_json::from_value(transactions)?;
ResponseResult::ListTransactions(result)
}
Method::GetBalance => {
let result: GetBalanceResponse = serde_json::from_value(result)?;
ResponseResult::GetBalance(result)
}
Method::GetInfo => {
let result: GetInfoResponse = serde_json::from_value(result)?;
ResponseResult::GetInfo(result)
}
Method::MakeHoldInvoice => {
let result: MakeHoldInvoiceResponse = serde_json::from_value(result)?;
ResponseResult::MakeHoldInvoice(result)
}
Method::CancelHoldInvoice => {
let result: CancelHoldInvoiceResponse = serde_json::from_value(result)?;
ResponseResult::CancelHoldInvoice(result)
}
Method::SettleHoldInvoice => {
let result: SettleHoldInvoiceResponse = serde_json::from_value(result)?;
ResponseResult::SettleHoldInvoice(result)
}
Method::Unknown(name) => {
return Err(Error::UnsupportedMethod(Method::Unknown(name)));
}
};
Ok(Self {
result_type: template.result_type,
error: template.error,
result: Some(result),
})
} else {
Ok(Self {
result_type: template.result_type,
error: template.error,
result: None,
})
}
}
pub fn to_pay_invoice(self) -> Result<PayInvoiceResponse, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::PayInvoice(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_pay_keysend(self) -> Result<PayKeysendResponse, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::PayKeysend(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_make_invoice(self) -> Result<MakeInvoiceResponse, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::MakeInvoice(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_lookup_invoice(self) -> Result<LookupInvoiceResponse, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::LookupInvoice(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_list_transactions(self) -> Result<Vec<LookupInvoiceResponse>, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::ListTransactions(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_get_balance(self) -> Result<GetBalanceResponse, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::GetBalance(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_get_info(self) -> Result<GetInfoResponse, Error> {
if let Some(e) = self.error {
return Err(Error::ErrorCode(e));
}
if let Some(ResponseResult::GetInfo(result)) = self.result {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
}
impl JsonUtil for Response {
type Err = Error;
}
impl<'de> Deserialize<'de> for Response {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
Self::from_value(value).map_err(serde::de::Error::custom)
}
}
#[inline]
fn url_encode<T>(data: T) -> String
where
T: AsRef<[u8]>,
{
byte_serialize(data.as_ref()).collect()
}
pub const NOSTR_WALLET_CONNECT_URI_SCHEME: &str = "nostr+walletconnect";
#[allow(missing_docs)]
#[deprecated(since = "0.45.0", note = "Use NostrWalletConnectUri instead")]
pub type NostrWalletConnectURI = NostrWalletConnectUri;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NostrWalletConnectUri {
pub public_key: PublicKey,
pub relays: Vec<RelayUrl>,
pub secret: SecretKey,
pub lud16: Option<String>,
}
impl NostrWalletConnectUri {
#[inline]
pub fn new(
public_key: PublicKey,
relays: Vec<RelayUrl>,
random_secret_key: SecretKey,
lud16: Option<String>,
) -> Self {
Self {
public_key,
relays,
secret: random_secret_key,
lud16,
}
}
pub fn parse<S>(uri: S) -> Result<Self, Error>
where
S: AsRef<str>,
{
let url: Url = Url::parse(uri.as_ref()).map_err(|_| Error::InvalidURI)?;
if url.scheme() != NOSTR_WALLET_CONNECT_URI_SCHEME {
return Err(Error::InvalidURI);
}
if let Some(pubkey) = url.domain() {
let public_key = PublicKey::from_hex(pubkey).map_err(|_| Error::InvalidURI)?;
let mut relays: Vec<RelayUrl> = Vec::new();
let mut secret: Option<SecretKey> = None;
let mut lud16: Option<String> = None;
for (key, value) in url.query_pairs() {
match key {
Cow::Borrowed("relay") => {
if let Ok(relay_url) = RelayUrl::parse(value.as_ref()) {
relays.push(relay_url);
}
}
Cow::Borrowed("secret") => {
secret = SecretKey::from_hex(value.as_ref()).ok();
}
Cow::Borrowed("lud16") => {
lud16 = Some(value.to_string());
}
_ => (),
}
}
if let (false, Some(secret)) = (relays.is_empty(), secret) {
return Ok(Self {
public_key,
relays,
secret,
lud16,
});
}
}
Err(Error::InvalidURI)
}
}
impl FromStr for NostrWalletConnectUri {
type Err = Error;
fn from_str(uri: &str) -> Result<Self, Self::Err> {
Self::parse(uri)
}
}
impl fmt::Display for NostrWalletConnectUri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut relays_str: String = String::new();
for relay_url in self.relays.iter() {
let relay_url: &str = relay_url.as_str_without_trailing_slash();
relays_str.push_str("&relay=");
relays_str.push_str(&url_encode(relay_url));
}
write!(
f,
"{NOSTR_WALLET_CONNECT_URI_SCHEME}://{}?secret={}{relays_str}",
self.public_key,
self.secret.to_secret_hex(),
)?;
if let Some(lud16) = &self.lud16 {
write!(f, "&lud16={}", url_encode(lud16))?;
}
Ok(())
}
}
impl Serialize for NostrWalletConnectUri {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'a> Deserialize<'a> for NostrWalletConnectUri {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
let uri: String = String::deserialize(deserializer)?;
Self::from_str(&uri).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum NotificationType {
#[serde(rename = "payment_received")]
PaymentReceived,
#[serde(rename = "payment_sent")]
PaymentSent,
#[serde(rename = "hold_invoice_accepted")]
HoldInvoiceAccepted,
}
impl fmt::Display for NotificationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NotificationType::PaymentReceived => write!(f, "payment_received"),
NotificationType::PaymentSent => write!(f, "payment_sent"),
NotificationType::HoldInvoiceAccepted => write!(f, "hold_invoice_accepted"),
}
}
}
impl FromStr for NotificationType {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"payment_received" => Ok(NotificationType::PaymentReceived),
"payment_sent" => Ok(NotificationType::PaymentSent),
"hold_invoice_accepted" => Ok(NotificationType::HoldInvoiceAccepted),
_ => Err(Error::InvalidURI),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Notification {
pub notification_type: NotificationType,
pub notification: NotificationResult,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NotificationTemplate {
pub notification_type: NotificationType,
pub notification: Value,
}
impl Notification {
#[inline]
pub fn from_event(uri: &NostrWalletConnectUri, event: &Event) -> Result<Self, Error> {
let decrypt_res: String = nip04::decrypt(&uri.secret, &event.pubkey, &event.content)?;
Self::from_json(decrypt_res)
}
pub fn from_value(value: Value) -> Result<Self, Error> {
let template: NotificationTemplate = serde_json::from_value(value)?;
let result = template.notification;
let result = match template.notification_type {
NotificationType::PaymentReceived => {
let result: PaymentNotification = serde_json::from_value(result)?;
NotificationResult::PaymentReceived(result)
}
NotificationType::PaymentSent => {
let result: PaymentNotification = serde_json::from_value(result)?;
NotificationResult::PaymentSent(result)
}
NotificationType::HoldInvoiceAccepted => {
let result: HoldInvoiceAcceptedNotification = serde_json::from_value(result)?;
NotificationResult::HoldInvoiceAccepted(result)
}
};
Ok(Self {
notification_type: template.notification_type,
notification: result,
})
}
pub fn to_pay_notification(self) -> Result<PaymentNotification, Error> {
if let NotificationResult::PaymentReceived(result) = self.notification {
return Ok(result);
}
if let NotificationResult::PaymentSent(result) = self.notification {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
pub fn to_holdinvoice_accepted_notification(
self,
) -> Result<HoldInvoiceAcceptedNotification, Error> {
if let NotificationResult::HoldInvoiceAccepted(result) = self.notification {
return Ok(result);
}
Err(Error::UnexpectedResult)
}
}
impl JsonUtil for Notification {
type Err = Error;
}
impl<'de> Deserialize<'de> for Notification {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
Self::from_value(value).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NotificationResult {
PaymentReceived(PaymentNotification),
PaymentSent(PaymentNotification),
HoldInvoiceAccepted(HoldInvoiceAcceptedNotification),
}
impl Serialize for NotificationResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
NotificationResult::PaymentReceived(p) => p.serialize(serializer),
NotificationResult::PaymentSent(p) => p.serialize(serializer),
NotificationResult::HoldInvoiceAccepted(p) => p.serialize(serializer),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct PaymentNotification {
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<TransactionType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<TransactionState>,
pub invoice: String,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description_hash: Option<String>,
pub preimage: String,
pub payment_hash: String,
pub amount: u64,
pub fees_paid: u64,
pub created_at: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<Timestamp>,
pub settled_at: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct HoldInvoiceAcceptedNotification {
#[serde(rename = "type")]
pub transaction_type: TransactionType,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<TransactionState>,
pub invoice: String,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
pub description_hash: Option<String>,
pub payment_hash: String,
pub amount: u64,
pub created_at: Timestamp,
pub expires_at: Timestamp,
pub settle_deadline: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
fn deserialize_empty_string_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
match opt {
Some(s) if s.is_empty() => Ok(None),
Some(s) => T::from_str(&s).map(Some).map_err(serde::de::Error::custom),
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use super::*;
#[test]
fn test_method_eq() {
assert_eq!(Method::GetBalance, Method::GetBalance);
assert_eq!(
Method::PayInvoice,
Method::Unknown(String::from("pay_invoice"))
);
assert_eq!(
Method::PayKeysend,
Method::Unknown(String::from("pay_keysend"))
);
assert_eq!(
Method::MakeInvoice,
Method::Unknown(String::from("make_invoice"))
);
assert_eq!(
Method::LookupInvoice,
Method::Unknown(String::from("lookup_invoice"))
);
assert_eq!(
Method::ListTransactions,
Method::Unknown(String::from("list_transactions"))
);
assert_eq!(
Method::GetBalance,
Method::Unknown(String::from("get_balance"))
);
assert_eq!(Method::GetInfo, Method::Unknown(String::from("get_info")));
assert_eq!(
Method::MakeHoldInvoice,
Method::Unknown(String::from("make_hold_invoice"))
);
assert_eq!(
Method::CancelHoldInvoice,
Method::Unknown(String::from("cancel_hold_invoice"))
);
assert_eq!(
Method::SettleHoldInvoice,
Method::Unknown(String::from("settle_hold_invoice"))
);
assert_eq!(
Method::Unknown(String::from("unknown_method")),
Method::Unknown(String::from("unknown_method"))
);
}
#[test]
fn test_method_ne() {
assert_ne!(Method::GetBalance, Method::GetInfo);
assert_ne!(Method::GetInfo, Method::Unknown(String::from("test")));
}
#[test]
fn test_uri() {
let pubkey =
PublicKey::from_str("b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4")
.unwrap();
let relay_url = RelayUrl::parse("wss://relay.damus.io").unwrap();
let secret =
SecretKey::from_str("71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c")
.unwrap();
let uri = NostrWalletConnectUri::new(
pubkey,
vec![relay_url],
secret,
Some("nostr@nostr.com".to_string()),
);
assert_eq!(
uri.to_string(),
"nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&relay=wss%3A%2F%2Frelay.damus.io&lud16=nostr%40nostr.com".to_string()
);
}
#[test]
fn test_parse_uri() {
let uri = "nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&relay=wss%3A%2F%2Frelay.damus.io&lud16=nostr%40nostr.com";
let uri = NostrWalletConnectUri::from_str(uri).unwrap();
let pubkey =
PublicKey::from_str("b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4")
.unwrap();
let relay_url = RelayUrl::parse("wss://relay.damus.io").unwrap();
let secret =
SecretKey::from_str("71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c")
.unwrap();
assert_eq!(
uri,
NostrWalletConnectUri::new(
pubkey,
vec![relay_url],
secret,
Some("nostr@nostr.com".to_string())
)
);
}
#[test]
fn test_get_info_request() {
let request = Request::get_info();
let json = request.as_json();
assert_eq!(json, "{\"method\":\"get_info\",\"params\":{}}");
assert_eq!(Request::from_json(json).unwrap(), request);
}
#[test]
fn test_get_balance_request() {
let request = Request::get_balance();
let json = request.as_json();
assert_eq!(json, "{\"method\":\"get_balance\",\"params\":{}}");
assert_eq!(Request::from_json(json).unwrap(), request);
}
#[test]
fn test_pay_invoice_request() {
let request = Request {
method: Method::PayInvoice,
params: RequestParams::PayInvoice(PayInvoiceRequest { id: None, invoice: "lnbc210n1pj99rx0pp5ehevgz9nf7d97h05fgkdeqxzytm6yuxd7048axru03fpzxxvzt7shp5gv7ef0s26pw5gy5dpwvsh6qgc8se8x2lmz2ev90l9vjqzcns6u6scqzzsxqyz5vqsp".to_string(), amount: None }),
};
assert_eq!(Request::from_json(request.as_json()).unwrap(), request);
assert_eq!(
request.as_json(),
"{\"method\":\"pay_invoice\",\"params\":{\"invoice\":\"lnbc210n1pj99rx0pp5ehevgz9nf7d97h05fgkdeqxzytm6yuxd7048axru03fpzxxvzt7shp5gv7ef0s26pw5gy5dpwvsh6qgc8se8x2lmz2ev90l9vjqzcns6u6scqzzsxqyz5vqsp\"}}"
);
}
#[test]
fn test_parse_request() {
let request = "{\"params\":{\"invoice\":\"lnbc210n1pj99rx0pp5ehevgz9nf7d97h05fgkdeqxzytm6yuxd7048axru03fpzxxvzt7shp5gv7ef0s26pw5gy5dpwvsh6qgc8se8x2lmz2ev90l9vjqzcns6u6scqzzsxqyz5vqsp5rdjyt9jr2avv2runy330766avkweqp30ndnyt9x6dp5juzn7q0nq9qyyssq2mykpgu04q0hlga228kx9v95meaqzk8a9cnvya305l4c353u3h04azuh9hsmd503x6jlzjrsqzark5dxx30s46vuatwzjhzmkt3j4tgqu35rms\"},\"method\":\"pay_invoice\"}";
let request = Request::from_json(request).unwrap();
assert_eq!(request.method, Method::PayInvoice);
if let RequestParams::PayInvoice(pay) = request.params {
assert_eq!(pay.invoice, "lnbc210n1pj99rx0pp5ehevgz9nf7d97h05fgkdeqxzytm6yuxd7048axru03fpzxxvzt7shp5gv7ef0s26pw5gy5dpwvsh6qgc8se8x2lmz2ev90l9vjqzcns6u6scqzzsxqyz5vqsp5rdjyt9jr2avv2runy330766avkweqp30ndnyt9x6dp5juzn7q0nq9qyyssq2mykpgu04q0hlga228kx9v95meaqzk8a9cnvya305l4c353u3h04azuh9hsmd503x6jlzjrsqzark5dxx30s46vuatwzjhzmkt3j4tgqu35rms".to_string());
} else {
panic!("Invalid request params");
}
}
#[test]
fn test_serialization_list_transactions() {
let response_result = Response {
result: Some(ResponseResult::ListTransactions(vec![
LookupInvoiceResponse {
transaction_type: Some(TransactionType::Incoming),
state: Some(TransactionState::Expired),
invoice: Some(String::from("abcd")),
description: Some(String::from("string")),
amount: 123,
fees_paid: 1,
created_at: Timestamp::from(123456),
expires_at: Some(Timestamp::from(1234567)),
description_hash: None,
payment_hash: String::new(),
metadata: None,
settled_at: None,
preimage: None,
},
])),
result_type: Method::ListTransactions,
error: None,
};
let response_result_json = serde_json::to_string(&response_result).unwrap();
let reponse_result_deserialized: Response =
serde_json::from_str(&response_result_json).unwrap();
assert_eq!(response_result, reponse_result_deserialized)
}
#[test]
fn test_parse_list_transactions_result() {
let json = r#"{
"result_type": "list_transactions",
"result": {
"transactions": [
{
"type": "incoming",
"state": "expired",
"invoice": "abcd",
"description": "string",
"payment_hash": "",
"amount": 123,
"fees_paid": 1,
"created_at": 123456,
"expires_at": 1234567
}
]
}
}"#;
let result = Response::from_json(json).unwrap();
assert_eq!(result.result_type, Method::ListTransactions);
assert!(result.error.is_none());
assert_eq!(
result.result,
Some(ResponseResult::ListTransactions(vec![
LookupInvoiceResponse {
transaction_type: Some(TransactionType::Incoming),
state: Some(TransactionState::Expired),
invoice: Some(String::from("abcd")),
description: Some(String::from("string")),
amount: 123,
fees_paid: 1,
created_at: Timestamp::from(123456),
expires_at: Some(Timestamp::from(1234567)),
description_hash: None,
payment_hash: String::new(),
metadata: None,
settled_at: None,
preimage: None
}
]))
)
}
#[test]
fn test_notifications_parse_and_serialization() {
let json = r#"{
"notification_type": "payment_received",
"notification": {
"type": "incoming",
"state": "settled",
"invoice": "abcd",
"description": "string1",
"description_hash": "string2",
"preimage": "string3",
"payment_hash": "string4",
"amount": 1234,
"fees_paid": 123,
"created_at": 123456789,
"expires_at": 546132287,
"settled_at": 843548111,
"metadata": {}
}
}"#;
let notification_parsed = Notification::from_json(json).unwrap();
assert_eq!(
notification_parsed.notification_type,
NotificationType::PaymentReceived
);
let notification_result = NotificationResult::PaymentReceived(PaymentNotification {
transaction_type: Some(TransactionType::Incoming),
state: Some(TransactionState::Settled),
invoice: String::from("abcd"),
description: Some(String::from("string1")),
description_hash: Some(String::from("string2")),
preimage: String::from("string3"),
payment_hash: String::from("string4"),
amount: 1234,
fees_paid: 123,
created_at: Timestamp::from_secs(123456789),
expires_at: Some(Timestamp::from_secs(546132287)),
settled_at: Timestamp::from_secs(843548111),
metadata: Some(Value::Object(serde_json::Map::new())),
});
assert_eq!(notification_parsed.notification, notification_result);
let notification_json = serde_json::to_string(¬ification_parsed).unwrap();
let reponse_result_deserialized: Notification =
serde_json::from_str(¬ification_json).unwrap();
assert_eq!(notification_parsed, reponse_result_deserialized)
}
#[test]
fn test_parse_get_info_response_with_empty_strings() {
let json = r#"{"alias":"","color":"","pubkey":"","network":"","block_height":0,"block_hash":"","methods":["pay_invoice","pay_keysend","get_info","get_balance"],"notifications":[],"lud16":""}"#;
let response: GetInfoResponse = serde_json::from_str(json).unwrap();
assert_eq!(
response,
GetInfoResponse {
alias: None,
color: None,
pubkey: None,
network: None,
block_height: Some(0),
block_hash: None,
methods: vec![
Method::PayInvoice,
Method::PayKeysend,
Method::GetInfo,
Method::GetBalance,
],
notifications: Vec::new()
}
);
}
}