#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
#![allow(unknown_lints)] #![allow(clippy::arc_with_non_send_sync)]
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
pub extern crate nostr;
pub extern crate nostr_zapper as zapper;
use async_utility::time;
use nostr::nips::nip47::{
GetBalanceResponseResult, GetInfoResponseResult, ListTransactionsRequestParams,
LookupInvoiceRequestParams, LookupInvoiceResponseResult, MakeInvoiceRequestParams,
MakeInvoiceResponseResult, NostrWalletConnectURI, PayInvoiceRequestParams,
PayInvoiceResponseResult, PayKeysendRequestParams, PayKeysendResponseResult, Request, Response,
};
use nostr::{Event, EventId, Filter, Kind, SubscriptionId, Timestamp};
use nostr_relay_pool::{Relay, RelayNotification, RelaySendOptions, SubscribeOptions};
use nostr_zapper::{async_trait, NostrZapper, ZapperBackend};
pub mod error;
pub mod options;
pub mod prelude;
#[doc(hidden)]
pub use self::error::Error;
#[doc(hidden)]
pub use self::options::NostrWalletConnectOptions;
const ID: &str = "nwc";
#[derive(Debug, Clone)]
pub struct NWC {
uri: NostrWalletConnectURI,
relay: Relay,
opts: NostrWalletConnectOptions,
initialized: Arc<AtomicBool>,
}
impl NWC {
#[inline]
pub fn new(uri: NostrWalletConnectURI) -> Self {
Self::with_opts(uri, NostrWalletConnectOptions::default())
}
pub fn with_opts(uri: NostrWalletConnectURI, opts: NostrWalletConnectOptions) -> Self {
Self {
relay: Relay::with_opts(uri.relay_url.clone(), opts.relay.clone()),
uri,
opts,
initialized: Arc::new(AtomicBool::new(false)),
}
}
#[inline]
fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::SeqCst)
}
async fn init(&self) -> Result<(), Error> {
if !self.is_initialized() {
self.relay.connect(Some(Duration::from_secs(10))).await;
let filter = Filter::new()
.author(self.uri.public_key)
.kind(Kind::WalletConnectResponse)
.since(Timestamp::now());
self.relay
.subscribe_with_id(
SubscriptionId::new(ID),
vec![filter],
SubscribeOptions::default(),
)
.await?;
self.initialized.store(true, Ordering::SeqCst);
}
Ok(())
}
async fn send_request(&self, req: Request) -> Result<Response, Error> {
self.init().await?;
let event: Event = req.to_event(&self.uri)?;
let event_id: EventId = event.id;
let mut notifications = self.relay.notifications();
self.relay
.send_event(event, RelaySendOptions::new())
.await?;
time::timeout(Some(self.opts.timeout), async {
while let Ok(notification) = notifications.recv().await {
if let RelayNotification::Event { event, .. } = notification {
if event.kind() == Kind::WalletConnectResponse
&& event.event_ids().next().copied() == Some(event_id)
{
return Ok(Response::from_event(&self.uri, &event)?);
}
}
}
Err(Error::Timeout)
})
.await
.ok_or(Error::Timeout)?
}
pub async fn pay_invoice<S>(&self, invoice: S) -> Result<String, Error>
where
S: Into<String>,
{
let req = Request::pay_invoice(PayInvoiceRequestParams {
id: None,
invoice: invoice.into(),
amount: None,
});
let res: Response = self.send_request(req).await?;
let PayInvoiceResponseResult { preimage } = res.to_pay_invoice()?;
Ok(preimage)
}
pub async fn pay_keysend(
&self,
params: PayKeysendRequestParams,
) -> Result<PayKeysendResponseResult, Error> {
let req = Request::pay_keysend(params);
let res: Response = self.send_request(req).await?;
Ok(res.to_pay_keysend()?)
}
pub async fn make_invoice(
&self,
params: MakeInvoiceRequestParams,
) -> Result<MakeInvoiceResponseResult, Error> {
let req: Request = Request::make_invoice(params);
let res: Response = self.send_request(req).await?;
Ok(res.to_make_invoice()?)
}
pub async fn lookup_invoice(
&self,
params: LookupInvoiceRequestParams,
) -> Result<LookupInvoiceResponseResult, Error> {
let req = Request::lookup_invoice(params);
let res: Response = self.send_request(req).await?;
Ok(res.to_lookup_invoice()?)
}
pub async fn list_transactions(
&self,
params: ListTransactionsRequestParams,
) -> Result<Vec<LookupInvoiceResponseResult>, Error> {
let req = Request::list_transactions(params);
let res: Response = self.send_request(req).await?;
Ok(res.to_list_transactions()?)
}
pub async fn get_balance(&self) -> Result<u64, Error> {
let req = Request::get_balance();
let res: Response = self.send_request(req).await?;
let GetBalanceResponseResult { balance } = res.to_get_balance()?;
Ok(balance)
}
pub async fn get_info(&self) -> Result<GetInfoResponseResult, Error> {
let req = Request::get_info();
let res: Response = self.send_request(req).await?;
Ok(res.to_get_info()?)
}
#[inline]
pub async fn shutdown(self) -> Result<(), Error> {
Ok(self.relay.disconnect().await?)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl NostrZapper for NWC {
type Err = Error;
#[inline]
fn backend(&self) -> ZapperBackend {
ZapperBackend::NWC
}
#[inline]
async fn pay(&self, invoice: String) -> Result<(), Self::Err> {
self.pay_invoice(invoice).await?;
Ok(())
}
}