Skip to content

Commit

Permalink
sdk: init ClientZapper
Browse files Browse the repository at this point in the history
  • Loading branch information
yukibtc committed Jan 19, 2024
1 parent b8d2670 commit 0de1552
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 11 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/nostr-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ nip11 = ["nostr/nip11"]
nip44 = ["nostr/nip44"]
nip46 = ["nostr/nip46"]
nip47 = ["nostr/nip47"]
nip57 = ["nostr/nip57"]
nip57 = ["nostr/nip57", "dep:lnurl-pay"]

[dependencies]
async-utility.workspace = true
async-wsocket = "0.1"
lnurl-pay = { version = "0.2", features = ["api"], optional = true }
nostr = { workspace = true, features = ["std"] }
nostr-database.workspace = true
once_cell.workspace = true
Expand All @@ -48,6 +49,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sy
[target.'cfg(target_arch = "wasm32")'.dependencies]
nostr-indexeddb = { version = "0.27", path = "../nostr-indexeddb", optional = true }
tokio = { workspace = true, features = ["rt", "macros", "sync"] }
webln = { version = "0.1", optional = true }

[dev-dependencies]
tracing-subscriber = { workspace = true, features = ["env-filter"] }
Expand Down
16 changes: 16 additions & 0 deletions crates/nostr-sdk/src/client/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ use nostr_database::memory::MemoryDatabase;
use nostr_database::{DynNostrDatabase, IntoNostrDatabase};

use super::signer::ClientSigner;
#[cfg(feature = "nip57")]
use super::zapper::ClientZapper;
use crate::{Client, Options};

/// Client builder
#[derive(Debug, Clone)]
pub struct ClientBuilder {
pub(super) signer: Option<ClientSigner>,
#[cfg(feature = "nip57")]
pub(super) zapper: Option<ClientZapper>,
pub(super) database: Arc<DynNostrDatabase>,
pub(super) opts: Options,
}
Expand All @@ -24,6 +28,8 @@ impl Default for ClientBuilder {
fn default() -> Self {
Self {
signer: None,
#[cfg(feature = "nip57")]
zapper: None,
database: Arc::new(MemoryDatabase::default()),
opts: Options::default(),
}
Expand Down Expand Up @@ -56,6 +62,16 @@ impl ClientBuilder {
self
}

/// Set zapper
#[cfg(feature = "nip57")]
pub fn zapper<S>(mut self, zapper: S) -> Self
where
S: Into<ClientZapper>,
{
self.zapper = Some(zapper.into());
self
}

/// Set database
pub fn database<D>(mut self, database: D) -> Self
where
Expand Down
56 changes: 56 additions & 0 deletions crates/nostr-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ pub mod blocking;
pub mod builder;
pub mod options;
pub mod signer;
#[cfg(feature = "nip57")]
pub mod zapper;

pub use self::builder::ClientBuilder;
pub use self::options::Options;
#[cfg(feature = "nip46")]
pub use self::signer::nip46::Nip46Signer;
pub use self::signer::{ClientSigner, ClientSignerType};
#[cfg(feature = "nip57")]
use self::zapper::ClientZapper;
use crate::relay::pool::{self, Error as RelayPoolError, RelayPool};
use crate::relay::{
FilterOptions, NegentropyOptions, Relay, RelayOptions, RelayPoolNotification, RelaySendOptions,
Expand Down Expand Up @@ -84,6 +88,10 @@ pub enum Error {
/// Found client signer type
found: ClientSignerType,
},
/// Zapper not configured
#[cfg(feature = "nip57")]
#[error("zapper not configured")]
ZapperNotConfigured,
/// NIP04 error
#[cfg(feature = "nip04")]
#[error(transparent)]
Expand All @@ -100,6 +108,13 @@ pub enum Error {
#[cfg(feature = "nip46")]
#[error(transparent)]
JSON(#[from] nostr::serde_json::Error),
/// LNURL Pay
#[cfg(feature = "nip57")]
#[error(transparent)]
LnUrlPay(#[from] lnurl_pay::Error),
#[cfg(all(feature = "webln", target_arch = "wasm32"))]
#[error(transparent)]
WebLN(#[from] webln::Error),
/// Generic NIP46 error
#[cfg(feature = "nip46")]
#[error("generic error")]
Expand All @@ -120,13 +135,21 @@ pub enum Error {
#[cfg(feature = "nip46")]
#[error("response not match to the request")]
ResponseNotMatchRequest,
/// Event not found
#[error("event not found: {0}")]
EventNotFound(EventId),
/// Impossible to zap
#[error("impossible to send zap: {0}")]
ImpossibleToZap(String),
}

/// Nostr client
#[derive(Debug, Clone)]
pub struct Client {
pool: RelayPool,
signer: Arc<RwLock<Option<ClientSigner>>>,
#[cfg(feature = "nip57")]
zapper: Arc<RwLock<Option<ClientZapper>>>,
opts: Options,
dropped: Arc<AtomicBool>,
}
Expand Down Expand Up @@ -202,6 +225,8 @@ impl Client {
Self {
pool: RelayPool::with_database(builder.opts.pool, builder.database),
signer: Arc::new(RwLock::new(builder.signer)),
#[cfg(feature = "nip57")]
zapper: Arc::new(RwLock::new(builder.zapper)),
opts: builder.opts,
dropped: Arc::new(AtomicBool::new(false)),
}
Expand All @@ -226,6 +251,22 @@ impl Client {
*s = signer;
}

/// Get current client zapper
///
/// Rise error if it not set.
#[cfg(feature = "nip57")]
pub async fn zapper(&self) -> Result<ClientZapper, Error> {
let zapper = self.zapper.read().await;
zapper.clone().ok_or(Error::ZapperNotConfigured)
}

/// Set client zapper
#[cfg(feature = "nip57")]
pub async fn set_zapper(&self, zapper: Option<ClientZapper>) {
let mut s = self.zapper.write().await;
*s = zapper;
}

/// Get current [`Keys`]
#[deprecated(since = "0.27.0", note = "Use `client.signer().await` instead.")]
pub async fn keys(&self) -> Keys {
Expand Down Expand Up @@ -757,6 +798,21 @@ impl Client {
self.send_event_to(url, event).await
}

/// Get public key metadata
///
/// <https://github.com/nostr-protocol/nips/blob/master/01.md>
pub async fn metadata(&self, public_key: XOnlyPublicKey) -> Result<Metadata, Error> {
let filter: Filter = Filter::new()
.author(public_key)
.kind(Kind::Metadata)
.limit(1);
let events: Vec<Event> = self.get_events_of(vec![filter], None).await?;
match events.first() {
Some(event) => Ok(Metadata::from_json(event.content())?),
None => Ok(Metadata::default()),
}
}

/// Update metadata
///
/// <https://github.com/nostr-protocol/nips/blob/master/01.md>
Expand Down
123 changes: 123 additions & 0 deletions crates/nostr-sdk/src/client/zapper/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

//! Client Zapper
use std::str::FromStr;

use lnurl_pay::api::Lud06OrLud16;
use lnurl_pay::{LightningAddress, LnUrl};
use nostr::nips::nip57::ZapType;
use nostr::secp256k1::XOnlyPublicKey;
use nostr::{Event, EventId, Filter, Metadata};
#[cfg(all(feature = "webln", target_arch = "wasm32"))]
use webln::WebLN;

use super::{Client, Error};

/// Zap entity
pub enum ZapEntity {
/// Zap to event
Event(EventId),
/// Zap to public key
PublicKey(XOnlyPublicKey),
/// Lightning Address
LUD16(LightningAddress),
/// LUD06
LUD06(LnUrl),
}

impl From<EventId> for ZapEntity {
fn from(value: EventId) -> Self {
Self::Event(value)
}
}

impl From<XOnlyPublicKey> for ZapEntity {
fn from(value: XOnlyPublicKey) -> Self {
Self::PublicKey(value)
}
}

/// Client Zapper
#[derive(Debug, Clone)]
pub enum ClientZapper {
/// WebLN
#[cfg(all(feature = "webln", target_arch = "wasm32"))]
WebLN(WebLN),
/// NWC (TODO)
NWC,
}

#[cfg(all(feature = "webln", target_arch = "wasm32"))]
impl From<WebLN> for ClientZapper {
fn from(value: WebLN) -> Self {
Self::WebLN(value)
}
}

impl Client {
/// Send a Zap!
pub async fn zap<T>(&self, to: T, satoshi: u64, r#_type: Option<ZapType>) -> Result<(), Error>
where
T: Into<ZapEntity>,
{
// Steps
// 1. Check if zapper is set and availabe
// 2. Get metadata of pubkey/author of event
// 3. Get invoice
// 4. Send payment

// Check zapper
let zapper: ClientZapper = self.zapper().await?;

// Get entity metadata
let to: ZapEntity = to.into();
let lud: Lud06OrLud16 = match to {
ZapEntity::Event(event_id) => {
// Get event
let filter: Filter = Filter::new().id(event_id);
let events: Vec<Event> = self.get_events_of(vec![filter], None).await?;
let event: &Event = events.first().ok_or(Error::EventNotFound(event_id))?;
let public_key: XOnlyPublicKey = event.author();
let metadata: Metadata = self.metadata(public_key).await?;

if let Some(lud16) = &metadata.lud16 {
LightningAddress::parse(lud16)?.into()
} else if let Some(lud06) = &metadata.lud06 {
LnUrl::from_str(lud06)?.into()
} else {
return Err(Error::ImpossibleToZap(String::from("LUD06/LUD16 not set")));
}
}
ZapEntity::PublicKey(public_key) => {
let metadata: Metadata = self.metadata(public_key).await?;

if let Some(lud16) = &metadata.lud16 {
LightningAddress::parse(lud16)?.into()
} else if let Some(lud06) = &metadata.lud06 {
LnUrl::from_str(lud06)?.into()
} else {
return Err(Error::ImpossibleToZap(String::from("LUD06/LUD16 not set")));
}
}
ZapEntity::LUD16(lnaddr) => lnaddr.into(),
ZapEntity::LUD06(lud06) => lud06.into(),
};

// Get invoice
let _invoice: String = lnurl_pay::api::get_invoice(lud, satoshi * 1000, None, None).await?;

match zapper {
#[cfg(all(feature = "webln", target_arch = "wasm32"))]
ClientZapper::WebLN(webln) => {
webln.enable().await?;
webln.send_payment(_invoice).await?;
}
ClientZapper::NWC => {}
};

Ok(())
}
}
2 changes: 2 additions & 0 deletions crates/nostr-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub use nostr_sqlite::{Error as SQLiteError, SQLiteDatabase};
use once_cell::sync::Lazy;
#[cfg(feature = "blocking")]
use tokio::runtime::Runtime;
#[cfg(all(feature = "webln", target_arch = "wasm32"))]
pub use webln;

pub mod client;
pub mod prelude;
Expand Down
20 changes: 10 additions & 10 deletions crates/nostr/src/nips/nip57.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,16 @@ impl From<secp256k1::Error> for Error {
}
}

// /// Zap Type
// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
// pub enum ZapType {
// Public
// Public,
// Private
// Private,
// Anonymous
// Anonymous,
// }
/// Zap Type
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ZapType {
/// Public
Public,
/// Private
Private,
/// Anonymous
Anonymous,
}

/// Zap Request Data
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down

0 comments on commit 0de1552

Please sign in to comment.