use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use super::nip04;
use crate::types::url::form_urlencoded::byte_serialize;
use crate::types::url::{ParseError, Url};
use crate::{key, Event, JsonUtil, PublicKey, SecretKey};
#[cfg(feature = "std")]
use crate::{EventBuilder, Keys, Kind, Tag};
#[derive(Debug)]
pub enum Error {
JSON(serde_json::Error),
Url(ParseError),
Keys(key::Error),
NIP04(nip04::Error),
#[cfg(feature = "std")]
EventBuilder(crate::event::builder::Error),
UnsignedEvent(crate::event::unsigned::Error),
ErrorCode(NIP47Error),
UnexpectedResult(String),
InvalidRequest,
InvalidParamsLength,
UnsupportedMethod(String),
InvalidURI,
InvalidURIScheme,
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::JSON(e) => write!(f, "Json: {e}"),
Self::Url(e) => write!(f, "Url: {e}"),
Self::Keys(e) => write!(f, "Keys: {e}"),
Self::NIP04(e) => write!(f, "NIP04: {e}"),
#[cfg(feature = "std")]
Self::EventBuilder(e) => write!(f, "Event Builder: {e}"),
Self::UnsignedEvent(e) => write!(f, "Unsigned event: {e}"),
Self::ErrorCode(e) => write!(f, "{e}"),
Self::UnexpectedResult(json) => write!(f, "Unexpected NIP47 result: {json}"),
Self::InvalidRequest => write!(f, "Invalid NIP47 Request"),
Self::InvalidParamsLength => write!(f, "Invalid NIP47 Params length"),
Self::UnsupportedMethod(e) => write!(f, "Unsupported method: {e}"),
Self::InvalidURI => write!(f, "Invalid NIP47 URI"),
Self::InvalidURIScheme => write!(f, "Invalid NIP47 URI Scheme"),
}
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Self::JSON(e)
}
}
impl From<ParseError> for Error {
fn from(e: ParseError) -> Self {
Self::Url(e)
}
}
impl From<key::Error> for Error {
fn from(e: key::Error) -> Self {
Self::Keys(e)
}
}
impl From<nip04::Error> for Error {
fn from(e: nip04::Error) -> Self {
Self::NIP04(e)
}
}
#[cfg(feature = "std")]
impl From<crate::event::builder::Error> for Error {
fn from(e: crate::event::builder::Error) -> Self {
Self::EventBuilder(e)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
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,
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Method::PayInvoice => write!(f, "pay_invoice"),
Method::MultiPayInvoice => write!(f, "multi_pay_invoice"),
Method::PayKeysend => write!(f, "pay_keysend"),
Method::MultiPayKeysend => write!(f, "multi_pay_keysend"),
Method::MakeInvoice => write!(f, "make_invoice"),
Method::LookupInvoice => write!(f, "lookup_invoice"),
Method::ListTransactions => write!(f, "list_transactions"),
Method::GetBalance => write!(f, "get_balance"),
Method::GetInfo => write!(f, "get_info"),
}
}
}
impl FromStr for Method {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"pay_invoice" => Ok(Method::PayInvoice),
"multi_pay_invoice" => Ok(Method::MultiPayInvoice),
"pay_keysend" => Ok(Method::PayKeysend),
"multi_pay_keysend" => Ok(Method::MultiPayKeysend),
"make_invoice" => Ok(Method::MakeInvoice),
"lookup_invoice" => Ok(Method::LookupInvoice),
"list_transactions" => Ok(Method::ListTransactions),
"get_balance" => Ok(Method::GetBalance),
"get_info" => Ok(Method::GetInfo),
_ => Err(Error::InvalidURI),
}
}
}
#[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, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Method {
#[serde(rename = "pay_invoice")]
PayInvoice,
#[serde(rename = "multi_pay_invoice")]
MultiPayInvoice,
#[serde(rename = "pay_keysend")]
PayKeysend,
#[serde(rename = "multi_pay_keysend")]
MultiPayKeysend,
#[serde(rename = "make_invoice")]
MakeInvoice,
#[serde(rename = "lookup_invoice")]
LookupInvoice,
#[serde(rename = "list_transactions")]
ListTransactions,
#[serde(rename = "get_balance")]
GetBalance,
#[serde(rename = "get_info")]
GetInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RequestParams {
PayInvoice(PayInvoiceRequestParams),
MultiPayInvoice(MultiPayInvoiceRequestParams),
PayKeysend(PayKeysendRequestParams),
MultiPayKeysend(MultiPayKeysendRequestParams),
MakeInvoice(MakeInvoiceRequestParams),
LookupInvoice(LookupInvoiceRequestParams),
ListTransactions(ListTransactionsRequestParams),
GetBalance,
GetInfo,
}
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::MultiPayInvoice(p) => p.serialize(serializer),
RequestParams::PayKeysend(p) => p.serialize(serializer),
RequestParams::MultiPayKeysend(p) => p.serialize(serializer),
RequestParams::MakeInvoice(p) => p.serialize(serializer),
RequestParams::LookupInvoice(p) => p.serialize(serializer),
RequestParams::ListTransactions(p) => p.serialize(serializer),
RequestParams::GetBalance => serializer.serialize_none(),
RequestParams::GetInfo => serializer.serialize_none(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PayInvoiceRequestParams {
#[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>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MultiPayInvoiceRequestParams {
pub invoices: Vec<PayInvoiceRequestParams>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct KeysendTLVRecord {
#[serde(rename = "type")]
pub tlv_type: u64,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PayKeysendRequestParams {
#[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, Serialize, Deserialize, PartialEq, Eq)]
pub struct MultiPayKeysendRequestParams {
pub keysends: Vec<PayKeysendRequestParams>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MakeInvoiceRequestParams {
pub amount: u64,
pub description: Option<String>,
pub description_hash: Option<String>,
pub expiry: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LookupInvoiceRequestParams {
pub payment_hash: Option<String>,
pub bolt11: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum TransactionType {
#[serde(rename = "incoming")]
Incoming,
#[serde(rename = "outgoing")]
Outgoing,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ListTransactionsRequestParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub until: Option<u64>,
#[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, Serialize, PartialEq, Eq)]
pub struct Request {
pub method: Method,
pub params: RequestParams,
}
#[derive(Serialize, Deserialize)]
struct RequestTemplate {
method: Method,
#[serde(default)] params: Value,
}
impl Request {
pub fn pay_invoice(params: PayInvoiceRequestParams) -> Self {
Self {
method: Method::PayInvoice,
params: RequestParams::PayInvoice(params),
}
}
pub fn pay_keysend(params: PayKeysendRequestParams) -> Self {
Self {
method: Method::PayKeysend,
params: RequestParams::PayKeysend(params),
}
}
pub fn make_invoice(params: MakeInvoiceRequestParams) -> Self {
Self {
method: Method::MakeInvoice,
params: RequestParams::MakeInvoice(params),
}
}
pub fn lookup_invoice(params: LookupInvoiceRequestParams) -> Self {
Self {
method: Method::LookupInvoice,
params: RequestParams::LookupInvoice(params),
}
}
pub fn list_transactions(params: ListTransactionsRequestParams) -> Self {
Self {
method: Method::ListTransactions,
params: RequestParams::ListTransactions(params),
}
}
pub fn get_balance() -> Self {
Self {
method: Method::GetBalance,
params: RequestParams::GetBalance,
}
}
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: PayInvoiceRequestParams = serde_json::from_value(template.params)?;
RequestParams::PayInvoice(params)
}
Method::MultiPayInvoice => {
let params: MultiPayInvoiceRequestParams = serde_json::from_value(template.params)?;
RequestParams::MultiPayInvoice(params)
}
Method::PayKeysend => {
let params: PayKeysendRequestParams = serde_json::from_value(template.params)?;
RequestParams::PayKeysend(params)
}
Method::MultiPayKeysend => {
let params: MultiPayKeysendRequestParams = serde_json::from_value(template.params)?;
RequestParams::MultiPayKeysend(params)
}
Method::MakeInvoice => {
let params: MakeInvoiceRequestParams = serde_json::from_value(template.params)?;
RequestParams::MakeInvoice(params)
}
Method::LookupInvoice => {
let params: LookupInvoiceRequestParams = serde_json::from_value(template.params)?;
RequestParams::LookupInvoice(params)
}
Method::ListTransactions => {
let params: ListTransactionsRequestParams =
serde_json::from_value(template.params)?;
RequestParams::ListTransactions(params)
}
Method::GetBalance => RequestParams::GetBalance,
Method::GetInfo => RequestParams::GetInfo,
};
Ok(Self {
method: template.method,
params,
})
}
#[cfg(feature = "std")]
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::public_key(uri.public_key)],
)
.to_event(&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 PayInvoiceResponseResult {
pub preimage: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct PayKeysendResponseResult {
pub preimage: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct MakeInvoiceResponseResult {
pub invoice: String,
pub payment_hash: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct LookupInvoiceResponseResult {
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<TransactionType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub invoice: Option<String>,
#[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 preimage: Option<String>,
pub payment_hash: String,
pub amount: u64,
pub fees_paid: u64,
pub created_at: u64,
pub expires_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub settled_at: Option<u64>,
pub metadata: Value,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct GetBalanceResponseResult {
pub balance: u64,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct GetInfoResponseResult {
pub alias: String,
pub color: String,
pub pubkey: String,
pub network: String,
pub block_height: u32,
pub block_hash: String,
pub methods: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseResult {
PayInvoice(PayInvoiceResponseResult),
MultiPayInvoice(PayInvoiceResponseResult),
PayKeysend(PayKeysendResponseResult),
MultiPayKeysend(PayKeysendResponseResult),
MakeInvoice(MakeInvoiceResponseResult),
LookupInvoice(LookupInvoiceResponseResult),
ListTransactions(Vec<LookupInvoiceResponseResult>),
GetBalance(GetBalanceResponseResult),
GetInfo(GetInfoResponseResult),
}
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::MultiPayInvoice(p) => p.serialize(serializer),
ResponseResult::PayKeysend(p) => p.serialize(serializer),
ResponseResult::MultiPayKeysend(p) => p.serialize(serializer),
ResponseResult::MakeInvoice(p) => p.serialize(serializer),
ResponseResult::LookupInvoice(p) => p.serialize(serializer),
ResponseResult::ListTransactions(p) => p.serialize(serializer),
ResponseResult::GetBalance(p) => p.serialize(serializer),
ResponseResult::GetInfo(p) => p.serialize(serializer),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Response {
pub result_type: Method,
pub error: Option<NIP47Error>,
pub result: Option<ResponseResult>,
}
#[derive(Debug, Clone, Deserialize)]
struct ResponseTemplate {
pub result_type: Method,
pub error: Option<NIP47Error>,
pub result: Option<Value>,
}
impl Response {
pub fn from_event(uri: &NostrWalletConnectURI, event: &Event) -> Result<Self, Error> {
let decrypt_res: String = nip04::decrypt(&uri.secret, event.author_ref(), event.content())?;
Self::from_json(decrypt_res)
}
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: PayInvoiceResponseResult = serde_json::from_value(result)?;
ResponseResult::PayInvoice(result)
}
Method::MultiPayInvoice => {
let result: PayInvoiceResponseResult = serde_json::from_value(result)?;
ResponseResult::MultiPayInvoice(result)
}
Method::PayKeysend => {
let result: PayKeysendResponseResult = serde_json::from_value(result)?;
ResponseResult::PayKeysend(result)
}
Method::MultiPayKeysend => {
let result: PayKeysendResponseResult = serde_json::from_value(result)?;
ResponseResult::MultiPayKeysend(result)
}
Method::MakeInvoice => {
let result: MakeInvoiceResponseResult = serde_json::from_value(result)?;
ResponseResult::MakeInvoice(result)
}
Method::LookupInvoice => {
let result: LookupInvoiceResponseResult = serde_json::from_value(result)?;
ResponseResult::LookupInvoice(result)
}
Method::ListTransactions => {
let result: Vec<LookupInvoiceResponseResult> = serde_json::from_value(result)?;
ResponseResult::ListTransactions(result)
}
Method::GetBalance => {
let result: GetBalanceResponseResult = serde_json::from_value(result)?;
ResponseResult::GetBalance(result)
}
Method::GetInfo => {
let result: GetInfoResponseResult = serde_json::from_value(result)?;
ResponseResult::GetInfo(result)
}
};
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<PayInvoiceResponseResult, 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(self.as_json()))
}
pub fn to_pay_keysend(self) -> Result<PayKeysendResponseResult, 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(self.as_json()))
}
pub fn to_make_invoice(self) -> Result<MakeInvoiceResponseResult, 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(self.as_json()))
}
pub fn to_lookup_invoice(self) -> Result<LookupInvoiceResponseResult, 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(self.as_json()))
}
pub fn to_list_transactions(self) -> Result<Vec<LookupInvoiceResponseResult>, 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(self.as_json()))
}
pub fn to_get_balance(self) -> Result<GetBalanceResponseResult, 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(self.as_json()))
}
pub fn to_get_info(self) -> Result<GetInfoResponseResult, 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(self.as_json()))
}
}
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)
}
}
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";
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NostrWalletConnectURI {
pub public_key: PublicKey,
pub relay_url: Url,
pub secret: SecretKey,
pub lud16: Option<String>,
}
impl NostrWalletConnectURI {
pub fn new(
public_key: PublicKey,
relay_url: Url,
random_secret_key: SecretKey,
lud16: Option<String>,
) -> Self {
Self {
public_key,
relay_url,
secret: random_secret_key,
lud16,
}
}
}
impl FromStr for NostrWalletConnectURI {
type Err = Error;
fn from_str(uri: &str) -> Result<Self, Self::Err> {
let url = Url::parse(uri)?;
if url.scheme() != NOSTR_WALLET_CONNECT_URI_SCHEME {
return Err(Error::InvalidURIScheme);
}
if let Some(pubkey) = url.domain() {
let public_key = PublicKey::from_str(pubkey)?;
let mut relay_url: Option<Url> = None;
let mut secret: Option<SecretKey> = None;
let mut lud16: Option<String> = None;
for (key, value) in url.query_pairs() {
match key {
Cow::Borrowed("relay") => {
let value = value.to_string();
relay_url = Some(Url::parse(&value)?);
}
Cow::Borrowed("secret") => {
let value = value.to_string();
secret = Some(SecretKey::from_str(&value)?);
}
Cow::Borrowed("lud16") => {
lud16 = Some(value.to_string());
}
_ => (),
}
}
if let Some(relay_url) = relay_url {
if let Some(secret) = secret {
return Ok(Self {
public_key,
relay_url,
secret,
lud16,
});
}
}
}
Err(Error::InvalidURI)
}
}
impl fmt::Display for NostrWalletConnectURI {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let relay_url = self.relay_url.to_string();
let relay_url = relay_url.strip_suffix('/').unwrap_or(&relay_url);
write!(
f,
"{NOSTR_WALLET_CONNECT_URI_SCHEME}://{}?relay={}&secret={}",
self.public_key,
url_encode(relay_url),
url_encode(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: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'a> Deserialize<'a> for NostrWalletConnectURI {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let uri = String::deserialize(deserializer)?;
NostrWalletConnectURI::from_str(&uri).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use super::*;
#[test]
fn test_uri() {
let pubkey =
PublicKey::from_str("b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4")
.unwrap();
let relay_url = Url::parse("wss://relay.damus.io").unwrap();
let secret =
SecretKey::from_str("71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c")
.unwrap();
let uri = NostrWalletConnectURI::new(
pubkey,
relay_url,
secret,
Some("nostr@nostr.com".to_string()),
);
assert_eq!(
uri.to_string(),
"nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&lud16=nostr%40nostr.com".to_string()
);
}
#[test]
fn test_parse_uri() {
let uri = "nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io%2F&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&lud16=nostr%40nostr.com";
let uri = NostrWalletConnectURI::from_str(uri).unwrap();
let pubkey =
PublicKey::from_str("b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4")
.unwrap();
let relay_url = Url::parse("wss://relay.damus.io").unwrap();
let secret =
SecretKey::from_str("71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c")
.unwrap();
assert_eq!(
uri,
NostrWalletConnectURI::new(
pubkey,
relay_url,
secret,
Some("nostr@nostr.com".to_string())
)
);
}
#[test]
fn serialize_request() {
let request = Request {
method: Method::PayInvoice,
params: RequestParams::PayInvoice(PayInvoiceRequestParams { 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");
}
}
}