mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-18 17:14:21 +01:00
@@ -54,7 +54,9 @@ pub use timecache::TimeCached;
|
||||
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
||||
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
||||
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
|
||||
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 zap;
|
||||
Reference in New Issue
Block a user