mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-19 01:24:21 +01:00
@@ -54,7 +54,9 @@ pub use timecache::TimeCached;
|
|||||||
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
||||||
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
||||||
pub use user_account::UserAccount;
|
pub use user_account::UserAccount;
|
||||||
pub use wallet::{GlobalWallet, Wallet, WalletError, WalletState, WalletType, WalletUIState};
|
pub use wallet::{
|
||||||
|
get_wallet_for_mut, GlobalWallet, Wallet, WalletError, WalletState, WalletType, WalletUIState,
|
||||||
|
};
|
||||||
|
|
||||||
// export libs
|
// export libs
|
||||||
pub use enostr;
|
pub use enostr;
|
||||||
|
|||||||
638
crates/notedeck/src/zaps/cache.rs
Normal file
638
crates/notedeck/src/zaps/cache.rs
Normal file
@@ -0,0 +1,638 @@
|
|||||||
|
use enostr::{NoteId, Pubkey};
|
||||||
|
use nostrdb::{Ndb, Transaction};
|
||||||
|
use nwc::nostr::nips::nip47::PayInvoiceResponse;
|
||||||
|
use poll_promise::Promise;
|
||||||
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
|
use crate::{get_wallet_for_mut, Accounts, GlobalWallet, ZapError};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
networking::{fetch_invoice_lnurl, fetch_invoice_lud16, FetchedInvoice, FetchingInvoice},
|
||||||
|
zap::Zap,
|
||||||
|
};
|
||||||
|
|
||||||
|
type ZapId = u32;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Zaps {
|
||||||
|
next_id: ZapId,
|
||||||
|
zap_keys: hashbrown::HashMap<ZapKeyOwned, Vec<ZapId>>,
|
||||||
|
// using `ZapId`s like this allows us to be flexible. in the future, we can also do cheap queries for any zaps from only specific senders or targets:
|
||||||
|
// zap_targets: hashbrown::HashMap<ZapTargetOwned, Vec<ZapId>>,
|
||||||
|
// zap_senders: hashbrown::HashMap<Pubkey, Vec<ZapId>>,
|
||||||
|
zaps: std::collections::HashMap<ZapId, ZapState>,
|
||||||
|
in_flight: Vec<ZapPromise>,
|
||||||
|
events: Vec<EventResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn process_event(
|
||||||
|
id: ZapId,
|
||||||
|
event: ZapEvent,
|
||||||
|
accounts: &mut Accounts,
|
||||||
|
global_wallet: &mut GlobalWallet,
|
||||||
|
ndb: &Ndb,
|
||||||
|
txn: &Transaction,
|
||||||
|
) -> NextState {
|
||||||
|
match event {
|
||||||
|
ZapEvent::FetchInvoice {
|
||||||
|
zap_ctx,
|
||||||
|
sender_relays,
|
||||||
|
} => process_new_zap_event(zap_ctx, accounts, ndb, txn, sender_relays),
|
||||||
|
ZapEvent::SendNWC {
|
||||||
|
zap_ctx,
|
||||||
|
req_noteid,
|
||||||
|
invoice,
|
||||||
|
} => {
|
||||||
|
let Some(wallet) = get_wallet_for_mut(accounts, global_wallet, &zap_ctx.key.sender)
|
||||||
|
else {
|
||||||
|
return NextState::Event(EventResponse {
|
||||||
|
id,
|
||||||
|
event: Err(ZappingError::SenderNoWallet),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let promise = wallet.pay_invoice(&invoice);
|
||||||
|
|
||||||
|
let ctx = SendingNWCInvoiceContext {
|
||||||
|
request_noteid: req_noteid,
|
||||||
|
zap_ctx,
|
||||||
|
};
|
||||||
|
NextState::Transition(ZapPromise::SendingNWCInvoice { ctx, promise })
|
||||||
|
}
|
||||||
|
ZapEvent::EndpointConfirmed {
|
||||||
|
zap_ctx,
|
||||||
|
req_noteid,
|
||||||
|
} => NextState::Success {
|
||||||
|
id: zap_ctx.id,
|
||||||
|
zap: LocalConfirmedZap {
|
||||||
|
request_noteid: req_noteid,
|
||||||
|
sender: zap_ctx.key.sender,
|
||||||
|
target: zap_ctx.key.target,
|
||||||
|
msats: zap_ctx.msats,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_new_zap_event(
|
||||||
|
zap_ctx: ZapCtx,
|
||||||
|
accounts: &Accounts,
|
||||||
|
ndb: &Ndb,
|
||||||
|
txn: &Transaction,
|
||||||
|
sender_relays: Vec<String>,
|
||||||
|
) -> NextState {
|
||||||
|
let Some(full_kp) = accounts
|
||||||
|
.get_selected_account()
|
||||||
|
.or_else(|| accounts.find_account(zap_ctx.key.sender.bytes()))
|
||||||
|
.and_then(|u| u.key.to_full())
|
||||||
|
else {
|
||||||
|
return NextState::Event(EventResponse {
|
||||||
|
id: zap_ctx.id,
|
||||||
|
event: Err(ZappingError::InvalidAccount),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(kernelkind): support ZapTarget::Profile
|
||||||
|
let ZapTargetOwned::Note(note_target) = zap_ctx.key.target.clone() else {
|
||||||
|
return NextState::Event(EventResponse {
|
||||||
|
id: zap_ctx.id,
|
||||||
|
event: Err(ZappingError::UnsupportedOperation),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = zap_ctx.id;
|
||||||
|
let promise = send_note_zap(
|
||||||
|
ndb,
|
||||||
|
txn,
|
||||||
|
note_target,
|
||||||
|
zap_ctx.msats,
|
||||||
|
&full_kp.secret_key.secret_bytes(),
|
||||||
|
sender_relays,
|
||||||
|
)
|
||||||
|
.map(|promise| ZapPromise::FetchingInvoice {
|
||||||
|
ctx: zap_ctx,
|
||||||
|
promise,
|
||||||
|
});
|
||||||
|
let Some(promise) = promise else {
|
||||||
|
return NextState::Event(EventResponse {
|
||||||
|
id,
|
||||||
|
event: Err(ZappingError::InvalidZapAddress),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
NextState::Transition(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn send_note_zap(
|
||||||
|
ndb: &Ndb,
|
||||||
|
txn: &Transaction,
|
||||||
|
target: NoteZapTargetOwned,
|
||||||
|
msats: u64,
|
||||||
|
nsec: &[u8; 32],
|
||||||
|
relays: Vec<String>,
|
||||||
|
) -> Option<FetchingInvoice> {
|
||||||
|
let address = get_users_zap_endpoint(txn, ndb, &target.zap_recipient)?;
|
||||||
|
|
||||||
|
let promise = match address {
|
||||||
|
ZapAddress::Lud16(s) => fetch_invoice_lud16(s, msats, *nsec, Some(target.note_id), relays),
|
||||||
|
ZapAddress::Lud06(s) => fetch_invoice_lnurl(s, msats, *nsec, Some(target.note_id), relays),
|
||||||
|
};
|
||||||
|
Some(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ZapAddress {
|
||||||
|
Lud16(String),
|
||||||
|
Lud06(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_users_zap_endpoint(txn: &Transaction, ndb: &Ndb, receiver: &Pubkey) -> Option<ZapAddress> {
|
||||||
|
let profile = ndb
|
||||||
|
.get_profile_by_pubkey(txn, receiver.bytes())
|
||||||
|
.ok()?
|
||||||
|
.record()
|
||||||
|
.profile()?;
|
||||||
|
|
||||||
|
profile
|
||||||
|
.lud06()
|
||||||
|
.map(|l| ZapAddress::Lud06(l.to_string()))
|
||||||
|
.or(profile.lud16().map(|l| ZapAddress::Lud16(l.to_string())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_get_promise_response(
|
||||||
|
promises: &mut Vec<ZapPromise>,
|
||||||
|
promise_index: usize, // this index must be guarenteed to exist
|
||||||
|
) -> Option<PromiseResponse> {
|
||||||
|
if !is_promise_ready(&promises[promise_index]) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise = promises.remove(promise_index);
|
||||||
|
|
||||||
|
match promise {
|
||||||
|
ZapPromise::FetchingInvoice { ctx, promise } => {
|
||||||
|
let result = promise.block_and_take();
|
||||||
|
|
||||||
|
Some(PromiseResponse::FetchingInvoice { ctx, result })
|
||||||
|
}
|
||||||
|
ZapPromise::SendingNWCInvoice { ctx, promise } => {
|
||||||
|
let result = promise.block_and_take();
|
||||||
|
|
||||||
|
Some(PromiseResponse::SendingNWCInvoice { ctx, result })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_promise_ready(zap_promise: &ZapPromise) -> bool {
|
||||||
|
match zap_promise {
|
||||||
|
ZapPromise::FetchingInvoice { ctx: _, promise } => promise.ready().is_some(),
|
||||||
|
ZapPromise::SendingNWCInvoice { ctx: _, promise } => promise.ready().is_some(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NextState {
|
||||||
|
Event(EventResponse),
|
||||||
|
Transition(ZapPromise),
|
||||||
|
Success { id: ZapId, zap: LocalConfirmedZap },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct EventResponse {
|
||||||
|
id: ZapId,
|
||||||
|
event: Result<ZapEvent, ZappingError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Zaps {
|
||||||
|
fn get_next_id(&mut self) -> ZapId {
|
||||||
|
let next = self.next_id;
|
||||||
|
self.next_id += 1;
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_event(&mut self, id: ZapId, event: ZapEvent) {
|
||||||
|
self.events.push(EventResponse {
|
||||||
|
id,
|
||||||
|
event: Ok(event),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_error(&mut self, sender_pubkey: &[u8; 32], target: ZapTarget, error: ZappingError) {
|
||||||
|
let id = self.get_next_id();
|
||||||
|
let key = ZapKey {
|
||||||
|
sender: sender_pubkey,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.insert_new_state(&id, &key, ZapState::Pending(Err(error)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_zap(
|
||||||
|
&mut self,
|
||||||
|
sender_pubkey: &[u8; 32],
|
||||||
|
sender_relays: Vec<String>,
|
||||||
|
target: ZapTarget,
|
||||||
|
msats: u64,
|
||||||
|
) {
|
||||||
|
let id = self.get_next_id();
|
||||||
|
let key = ZapKey {
|
||||||
|
sender: sender_pubkey,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
let event = ZapEvent::FetchInvoice {
|
||||||
|
zap_ctx: ZapCtx {
|
||||||
|
id,
|
||||||
|
key: (&key).into(),
|
||||||
|
msats,
|
||||||
|
},
|
||||||
|
sender_relays,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.insert_new_state(&id, &key, ZapState::Pending(Ok(event.clone())));
|
||||||
|
self.send_event(id, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_new_state(&mut self, id: &ZapId, key: &ZapKey, state: ZapState) {
|
||||||
|
self.zaps.insert(*id, state);
|
||||||
|
|
||||||
|
let Some(states) = self.zap_keys.get_mut(key) else {
|
||||||
|
let states: Vec<ZapId> = vec![*id];
|
||||||
|
self.zap_keys.insert((key).into(), states);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
states.push(*id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(
|
||||||
|
&mut self,
|
||||||
|
accounts: &mut Accounts,
|
||||||
|
global_wallet: &mut GlobalWallet,
|
||||||
|
ndb: &Ndb,
|
||||||
|
) {
|
||||||
|
for i in (0..self.in_flight.len()).rev() {
|
||||||
|
let Some(resp) = try_get_promise_response(&mut self.in_flight, i) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.events.push(resp.take_as_event_response());
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(event_resp) = self.events.pop() {
|
||||||
|
let event = match event_resp.event {
|
||||||
|
Ok(ev) => ev,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("transitioned to error for id {}: {e}", event_resp.id);
|
||||||
|
self.zaps.insert(event_resp.id, ZapState::Pending(Err(e)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let txn = nostrdb::Transaction::new(ndb).expect("txn");
|
||||||
|
match process_event(event_resp.id, event, accounts, global_wallet, ndb, &txn) {
|
||||||
|
NextState::Event(event_resp) => {
|
||||||
|
self.zaps
|
||||||
|
.insert(event_resp.id, ZapState::Pending(event_resp.event));
|
||||||
|
}
|
||||||
|
NextState::Transition(in_flight_promise) => {
|
||||||
|
self.in_flight.push(in_flight_promise);
|
||||||
|
}
|
||||||
|
NextState::Success { id, zap } => {
|
||||||
|
self.zaps.insert(id, ZapState::LocalConfirm(zap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_states_for<'a>(
|
||||||
|
&'a self,
|
||||||
|
sender: &[u8; 32],
|
||||||
|
target: ZapTarget<'a>,
|
||||||
|
) -> Option<Vec<&'a ZapState>> {
|
||||||
|
let key = ZapKey { sender, target };
|
||||||
|
let ids = self.zap_keys.get(&key)?;
|
||||||
|
|
||||||
|
let mut states = Vec::new();
|
||||||
|
for id in ids {
|
||||||
|
if let Some(state) = self.zaps.get(id) {
|
||||||
|
states.push(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if states.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(states)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// if any of the states are `ZapState::Pending`, all other values will be ignored and `AnyZapState::Pending` will return
|
||||||
|
/// if there is at least one `ZapState::LocalConfirm`, `AnyZapState::LocalOnly` will return
|
||||||
|
/// if there are `ZapState::Confirm` and none others, `AnyZapState::Confirmed` will return
|
||||||
|
/// otherwise `AnyZapState::None` will return
|
||||||
|
pub fn any_zap_state_for<'a>(
|
||||||
|
&'a self,
|
||||||
|
sender: &[u8; 32],
|
||||||
|
target: ZapTarget<'a>,
|
||||||
|
) -> AnyZapState {
|
||||||
|
let key = ZapKey { sender, target };
|
||||||
|
let Some(ids) = self.zap_keys.get(&key) else {
|
||||||
|
return AnyZapState::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut has_confirmed = false;
|
||||||
|
let mut has_local_confirmed = false;
|
||||||
|
|
||||||
|
for id in ids {
|
||||||
|
let Some(state) = self.zaps.get(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
match state {
|
||||||
|
ZapState::Confirm(_) => {
|
||||||
|
has_confirmed = true;
|
||||||
|
}
|
||||||
|
ZapState::LocalConfirm(_) => {
|
||||||
|
has_local_confirmed = true;
|
||||||
|
}
|
||||||
|
ZapState::Pending(p) => {
|
||||||
|
if let Err(e) = p {
|
||||||
|
return AnyZapState::Error(e.to_owned());
|
||||||
|
}
|
||||||
|
return AnyZapState::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_local_confirmed {
|
||||||
|
return AnyZapState::LocalOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_confirmed {
|
||||||
|
AnyZapState::Confirmed
|
||||||
|
} else {
|
||||||
|
AnyZapState::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_error_for(&mut self, sender: &[u8; 32], target: ZapTarget<'_>) {
|
||||||
|
let key = ZapKey { sender, target };
|
||||||
|
let Some(ids) = self.zap_keys.get_mut(&key) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
ids.retain(|id| {
|
||||||
|
let should_keep = !matches!(self.zaps.get(id), Some(ZapState::Pending(Err(_))));
|
||||||
|
if !should_keep {
|
||||||
|
self.zaps.remove(id);
|
||||||
|
}
|
||||||
|
should_keep
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AnyZapState {
|
||||||
|
None,
|
||||||
|
Pending,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Error(ZappingError),
|
||||||
|
LocalOnly,
|
||||||
|
Confirmed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ZapState {
|
||||||
|
Confirm(Zap),
|
||||||
|
LocalConfirm(LocalConfirmedZap),
|
||||||
|
Pending(Result<ZapEvent, ZappingError>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LocalConfirmedZap {
|
||||||
|
request_noteid: NoteId,
|
||||||
|
sender: Pubkey,
|
||||||
|
target: ZapTargetOwned,
|
||||||
|
msats: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
struct ZapKeyOwned {
|
||||||
|
sender: Pubkey,
|
||||||
|
target: ZapTargetOwned,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
struct ZapKey<'a> {
|
||||||
|
sender: &'a [u8; 32],
|
||||||
|
target: ZapTarget<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SendingNWCInvoiceContext {
|
||||||
|
request_noteid: NoteId,
|
||||||
|
zap_ctx: ZapCtx,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ZapCtx {
|
||||||
|
id: ZapId,
|
||||||
|
key: ZapKeyOwned,
|
||||||
|
msats: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ZapEvent {
|
||||||
|
FetchInvoice {
|
||||||
|
zap_ctx: ZapCtx,
|
||||||
|
sender_relays: Vec<String>,
|
||||||
|
},
|
||||||
|
SendNWC {
|
||||||
|
zap_ctx: ZapCtx,
|
||||||
|
req_noteid: NoteId,
|
||||||
|
invoice: String,
|
||||||
|
},
|
||||||
|
EndpointConfirmed {
|
||||||
|
zap_ctx: ZapCtx,
|
||||||
|
req_noteid: NoteId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ZappingError {
|
||||||
|
InvoiceFetchFailed(ZapError),
|
||||||
|
InvalidAccount,
|
||||||
|
UnsupportedOperation, // TODO(kernelkind): support profile zaps
|
||||||
|
InvalidZapAddress,
|
||||||
|
SenderNoWallet,
|
||||||
|
InvalidNWCResponse(String),
|
||||||
|
FutureError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ZappingError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ZappingError::InvoiceFetchFailed(err) => write!(f, "Failed to fetch invoice: {}", err),
|
||||||
|
ZappingError::InvalidAccount => write!(f, "Invalid account"),
|
||||||
|
ZappingError::UnsupportedOperation => {
|
||||||
|
write!(f, "Unsupported operation (e.g. profile zaps)")
|
||||||
|
}
|
||||||
|
ZappingError::InvalidZapAddress => write!(f, "Invalid zap address"),
|
||||||
|
ZappingError::SenderNoWallet => write!(f, "Sender has no wallet"),
|
||||||
|
ZappingError::InvalidNWCResponse(msg) => write!(f, "Invalid NWC response: {}", msg),
|
||||||
|
ZappingError::FutureError(msg) => write!(f, "Future error: {}", msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ZapPromise {
|
||||||
|
FetchingInvoice {
|
||||||
|
ctx: ZapCtx,
|
||||||
|
promise: Promise<Result<Result<FetchedInvoice, ZapError>, JoinError>>,
|
||||||
|
},
|
||||||
|
SendingNWCInvoice {
|
||||||
|
ctx: SendingNWCInvoiceContext,
|
||||||
|
promise: Promise<Result<PayInvoiceResponse, nwc::Error>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PromiseResponse {
|
||||||
|
FetchingInvoice {
|
||||||
|
ctx: ZapCtx,
|
||||||
|
result: Result<Result<FetchedInvoice, ZapError>, JoinError>,
|
||||||
|
},
|
||||||
|
SendingNWCInvoice {
|
||||||
|
ctx: SendingNWCInvoiceContext,
|
||||||
|
result: Result<PayInvoiceResponse, nwc::Error>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromiseResponse {
|
||||||
|
pub fn take_as_event_response(self) -> EventResponse {
|
||||||
|
match self {
|
||||||
|
PromiseResponse::FetchingInvoice { ctx, result } => {
|
||||||
|
let id = ctx.id;
|
||||||
|
let event = match result {
|
||||||
|
Ok(r) => match r {
|
||||||
|
Ok(invoice) => Ok(ZapEvent::SendNWC {
|
||||||
|
zap_ctx: ctx,
|
||||||
|
req_noteid: invoice.request_noteid,
|
||||||
|
invoice: invoice.invoice,
|
||||||
|
}),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("NWC error: {e}");
|
||||||
|
Err(ZappingError::InvoiceFetchFailed(e))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => Err(ZappingError::FutureError(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
EventResponse { id, event }
|
||||||
|
}
|
||||||
|
PromiseResponse::SendingNWCInvoice { ctx, result } => {
|
||||||
|
let id = ctx.zap_ctx.id;
|
||||||
|
let event = match result {
|
||||||
|
Ok(_) => Ok(ZapEvent::EndpointConfirmed {
|
||||||
|
zap_ctx: ctx.zap_ctx,
|
||||||
|
req_noteid: ctx.request_noteid,
|
||||||
|
}),
|
||||||
|
Err(e) => Err(ZappingError::InvalidNWCResponse(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
EventResponse { id, event }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
|
enum ZapTargetOwned {
|
||||||
|
Profile(Pubkey),
|
||||||
|
Note(NoteZapTargetOwned),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub enum ZapTarget<'a> {
|
||||||
|
Profile(&'a [u8; 32]),
|
||||||
|
Note(NoteZapTarget<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl ZapTargetOwned {
|
||||||
|
pub fn pubkey(&self) -> &Pubkey {
|
||||||
|
match &self {
|
||||||
|
ZapTargetOwned::Profile(pubkey) => pubkey,
|
||||||
|
ZapTargetOwned::Note(note_zap_target) => ¬e_zap_target.zap_recipient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
|
pub struct NoteZapTargetOwned {
|
||||||
|
pub note_id: NoteId,
|
||||||
|
pub zap_recipient: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct NoteZapTarget<'a> {
|
||||||
|
pub note_id: &'a [u8; 32],
|
||||||
|
pub zap_recipient: &'a [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&NoteZapTarget<'_>> for NoteZapTargetOwned {
|
||||||
|
fn from(value: &NoteZapTarget) -> Self {
|
||||||
|
Self {
|
||||||
|
note_id: NoteId::new(*value.note_id),
|
||||||
|
zap_recipient: Pubkey::new(*value.zap_recipient),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a NoteZapTargetOwned> for NoteZapTarget<'a> {
|
||||||
|
fn from(value: &'a NoteZapTargetOwned) -> Self {
|
||||||
|
Self {
|
||||||
|
note_id: value.note_id.bytes(),
|
||||||
|
zap_recipient: value.zap_recipient.bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ZapTarget<'_>> for ZapTargetOwned {
|
||||||
|
fn from(value: &ZapTarget) -> Self {
|
||||||
|
match value {
|
||||||
|
ZapTarget::Profile(pubkey) => ZapTargetOwned::Profile(Pubkey::new(**pubkey)),
|
||||||
|
ZapTarget::Note(note_zap_target) => ZapTargetOwned::Note(note_zap_target.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ZapKey<'_>> for ZapKeyOwned {
|
||||||
|
fn from(value: &ZapKey) -> Self {
|
||||||
|
Self {
|
||||||
|
sender: Pubkey::new(*value.sender),
|
||||||
|
target: (&value.target).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl hashbrown::Equivalent<ZapKeyOwned> for ZapKey<'_> {
|
||||||
|
fn equivalent(&self, key: &ZapKeyOwned) -> bool {
|
||||||
|
if key.sender.bytes() != self.sender {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (&self.target, &key.target) {
|
||||||
|
(ZapTarget::Profile(a), ZapTargetOwned::Profile(b)) => *a == b.bytes(),
|
||||||
|
(ZapTarget::Note(a), ZapTargetOwned::Note(b)) => {
|
||||||
|
a.note_id == b.note_id.bytes() && a.zap_recipient == b.zap_recipient.bytes()
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
mod cache;
|
||||||
mod networking;
|
mod networking;
|
||||||
mod zap;
|
mod zap;
|
||||||
Reference in New Issue
Block a user