use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use core::num::ParseIntError;
use super::nip01::Coordinate;
use super::util::{
take_and_parse_from_str, take_and_parse_optional_from_str, take_public_key, take_relay_url,
take_string,
};
use crate::event::builder::Error as BuilderError;
use crate::event::tag::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions};
use crate::key::Error as KeyError;
use crate::types::url;
use crate::{EventId, PublicKey, RelayUrl, event};
const AMOUNT: &str = "amount";
const BOLT11: &str = "bolt11";
const DESCRIPTION: &str = "description";
const LNURL: &str = "lnurl";
const PREIMAGE: &str = "preimage";
const RELAYS: &str = "relays";
const SENDER: &str = "P";
const ZAP: &str = "zap";
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Error {
Key(KeyError),
Builder(BuilderError),
Event(event::Error),
Url(url::Error),
ParseInt(ParseIntError),
Bech32Decode(bech32::DecodeError),
Bech32Encode(bech32::EncodeError),
Codec(TagCodecError),
InvalidPrivateZapMessage,
PrivateZapMessageNotFound,
WrongBech32Prefix,
WrongBlockMode,
}
impl core::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Key(e) => e.fmt(f),
Self::Builder(e) => e.fmt(f),
Self::Event(e) => e.fmt(f),
Self::Url(e) => e.fmt(f),
Self::ParseInt(e) => e.fmt(f),
Self::Bech32Decode(e) => e.fmt(f),
Self::Bech32Encode(e) => e.fmt(f),
Self::Codec(e) => e.fmt(f),
Self::InvalidPrivateZapMessage => f.write_str("Invalid private zap message"),
Self::PrivateZapMessageNotFound => f.write_str("Private zap message not found"),
Self::WrongBech32Prefix => f.write_str("Wrong bech32 prefix"),
Self::WrongBlockMode => f.write_str(
"Wrong encryption block mode. The content must be encrypted using CBC mode!",
),
}
}
}
impl From<KeyError> for Error {
fn from(e: KeyError) -> Self {
Self::Key(e)
}
}
impl From<BuilderError> for Error {
fn from(e: BuilderError) -> Self {
Self::Builder(e)
}
}
impl From<event::Error> for Error {
fn from(e: event::Error) -> Self {
Self::Event(e)
}
}
impl From<url::Error> for Error {
fn from(e: url::Error) -> Self {
Self::Url(e)
}
}
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Self {
Self::ParseInt(e)
}
}
impl From<bech32::DecodeError> for Error {
fn from(e: bech32::DecodeError) -> Self {
Self::Bech32Decode(e)
}
}
impl From<bech32::EncodeError> for Error {
fn from(e: bech32::EncodeError) -> Self {
Self::Bech32Encode(e)
}
}
impl From<TagCodecError> for Error {
fn from(e: TagCodecError) -> Self {
Self::Codec(e)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Nip57Tag {
Relays(Vec<RelayUrl>),
Amount {
millisats: u64,
bolt11: Option<String>,
},
Lnurl(String),
Bolt11(String),
Description(String),
Preimage(String),
Sender(PublicKey),
Zap {
public_key: PublicKey,
relay_url: RelayUrl,
weight: Option<u64>,
},
}
impl TagCodec for Nip57Tag {
type Error = Error;
fn parse<I, S>(tag: I) -> Result<Self, Self::Error>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut iter = tag.into_iter();
let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?;
match kind.as_ref() {
RELAYS => Ok(Self::Relays(parse_relays(iter)?)),
AMOUNT => {
let (millisats, bolt11) = parse_amount_tag(iter)?;
Ok(Self::Amount { millisats, bolt11 })
}
LNURL => Ok(Self::Lnurl(take_string(&mut iter, "LNURL")?)),
BOLT11 => Ok(Self::Bolt11(take_string(&mut iter, "BOLT11")?)),
DESCRIPTION => Ok(Self::Description(take_string(&mut iter, "description")?)),
PREIMAGE => Ok(Self::Preimage(take_string(&mut iter, "preimage")?)),
SENDER => {
let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?;
Ok(Self::Sender(public_key))
}
ZAP => {
let (public_key, relay_url, weight) = parse_zap_tag(iter)?;
Ok(Self::Zap {
public_key,
relay_url,
weight,
})
}
_ => Err(TagCodecError::Unknown.into()),
}
}
fn to_tag(&self) -> Tag {
match self {
Self::Relays(relays) => {
let mut tag: Vec<String> = Vec::with_capacity(relays.len() + 1);
tag.push(String::from(RELAYS));
tag.extend(relays.iter().map(ToString::to_string));
Tag::new(tag)
}
Self::Amount { millisats, bolt11 } => {
let mut tag: Vec<String> = vec![String::from(AMOUNT), millisats.to_string()];
if let Some(bolt11) = bolt11 {
tag.push(bolt11.clone());
}
Tag::new(tag)
}
Self::Lnurl(lnurl) => Tag::new(vec![String::from(LNURL), lnurl.clone()]),
Self::Bolt11(bolt11) => Tag::new(vec![String::from(BOLT11), bolt11.clone()]),
Self::Description(description) => {
Tag::new(vec![String::from(DESCRIPTION), description.clone()])
}
Self::Preimage(preimage) => Tag::new(vec![String::from(PREIMAGE), preimage.clone()]),
Self::Sender(public_key) => Tag::new(vec![String::from(SENDER), public_key.to_hex()]),
Self::Zap {
public_key,
relay_url,
weight,
} => {
let mut tag: Vec<String> = vec![
String::from(ZAP),
public_key.to_hex(),
relay_url.to_string(),
];
if let Some(weight) = weight {
tag.push(weight.to_string());
}
Tag::new(tag)
}
}
}
}
impl_tag_codec_conversions!(Nip57Tag);
fn parse_relays<T, S>(iter: T) -> Result<Vec<RelayUrl>, Error>
where
T: Iterator<Item = S>,
S: AsRef<str>,
{
let mut relays: Vec<RelayUrl> = Vec::new();
for relay in iter {
relays.push(RelayUrl::parse(relay.as_ref())?);
}
Ok(relays)
}
fn parse_amount_tag<T, S>(mut iter: T) -> Result<(u64, Option<String>), Error>
where
T: Iterator<Item = S>,
S: AsRef<str>,
{
let millisats: u64 = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "amount")?;
let bolt11: Option<String> = iter.next().map(|bolt11| bolt11.as_ref().to_string());
Ok((millisats, bolt11))
}
fn parse_zap_tag<T, S>(mut iter: T) -> Result<(PublicKey, RelayUrl, Option<u64>), Error>
where
T: Iterator<Item = S>,
S: AsRef<str>,
{
let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?;
let relay_url: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?;
let weight: Option<u64> = take_and_parse_optional_from_str(&mut iter)?;
Ok((public_key, relay_url, weight))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ZapType {
Public,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ZapRequestData {
pub public_key: PublicKey,
pub relays: Vec<RelayUrl>,
pub message: String,
pub amount: Option<u64>,
pub lnurl: Option<String>,
pub event_id: Option<EventId>,
pub event_coordinate: Option<Coordinate>,
}
impl ZapRequestData {
pub fn new<I>(public_key: PublicKey, relays: I) -> Self
where
I: IntoIterator<Item = RelayUrl>,
{
Self {
public_key,
relays: relays.into_iter().collect(),
message: String::new(),
amount: None,
lnurl: None,
event_id: None,
event_coordinate: None,
}
}
pub fn message<S>(self, message: S) -> Self
where
S: Into<String>,
{
Self {
message: message.into(),
..self
}
}
pub fn amount(self, amount: u64) -> Self {
Self {
amount: Some(amount),
..self
}
}
pub fn lnurl<S>(self, lnurl: S) -> Self
where
S: Into<String>,
{
Self {
lnurl: Some(lnurl.into()),
..self
}
}
pub fn event_id(self, event_id: EventId) -> Self {
Self {
event_id: Some(event_id),
..self
}
}
pub fn event_coordinate(self, event_coordinate: Coordinate) -> Self {
Self {
event_coordinate: Some(event_coordinate),
..self
}
}
}
impl From<ZapRequestData> for Vec<Tag> {
fn from(data: ZapRequestData) -> Self {
let ZapRequestData {
public_key,
relays,
amount,
lnurl,
event_id,
event_coordinate,
..
} = data;
let mut tags: Vec<Tag> = vec![Tag::public_key(public_key)];
if !relays.is_empty() {
tags.push(Nip57Tag::Relays(relays).into());
}
if let Some(event_id) = event_id {
tags.push(Tag::event(event_id));
}
if let Some(event_coordinate) = event_coordinate {
tags.push(event_coordinate.into());
}
if let Some(amount) = amount {
tags.push(
Nip57Tag::Amount {
millisats: amount,
bolt11: None,
}
.into(),
);
}
if let Some(lnurl) = lnurl {
tags.push(Nip57Tag::Lnurl(lnurl).into());
}
tags
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relays_tag() {
let tag = vec!["relays", "wss://relay.damus.io", "wss://relay.primal.net"];
let parsed = Nip57Tag::parse(tag).unwrap();
assert_eq!(
parsed,
Nip57Tag::Relays(vec![
RelayUrl::parse("wss://relay.damus.io").unwrap(),
RelayUrl::parse("wss://relay.primal.net").unwrap(),
])
);
assert_eq!(
parsed.to_tag(),
Tag::parse(["relays", "wss://relay.damus.io", "wss://relay.primal.net"]).unwrap()
);
}
#[test]
fn test_amount_tag() {
let tag = vec!["amount", "21000", "lnbc21u1p0test"];
let parsed = Nip57Tag::parse(tag).unwrap();
assert_eq!(
parsed,
Nip57Tag::Amount {
millisats: 21000,
bolt11: Some(String::from("lnbc21u1p0test")),
}
);
assert_eq!(
parsed.to_tag(),
Tag::parse(["amount", "21000", "lnbc21u1p0test"]).unwrap()
);
}
#[test]
fn test_sender_tag() {
let public_key =
PublicKey::from_hex("97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322")
.unwrap();
let parsed = Nip57Tag::parse([
"P",
"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
])
.unwrap();
assert_eq!(parsed, Nip57Tag::Sender(public_key));
assert_eq!(
parsed.to_tag(),
Tag::parse([
"P",
"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
])
.unwrap()
);
}
#[test]
fn test_zap_tag() {
let public_key =
PublicKey::from_hex("82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2")
.unwrap();
let relay_url = RelayUrl::parse("wss://nostr.oxtr.dev").unwrap();
let parsed = Nip57Tag::parse([
"zap",
"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
"wss://nostr.oxtr.dev",
"2",
])
.unwrap();
assert_eq!(
parsed,
Nip57Tag::Zap {
public_key,
relay_url,
weight: Some(2),
}
);
assert_eq!(
parsed.to_tag(),
Tag::parse([
"zap",
"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
"wss://nostr.oxtr.dev",
"2",
])
.unwrap()
);
}
}