use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
pub trait PaidAddressStore: Send + Sync {
fn contains(&self, store_key: &str, address: &str) -> bool;
fn record_success(&self, store_key: &str, address: &str);
fn has_used_nonce(&self, nonce: &str) -> bool;
fn record_nonce(&self, nonce: &str);
#[must_use]
fn consume_nonce(&self, nonce: &str) -> bool;
}
#[derive(Debug, Default)]
struct Inner {
paid: HashMap<String, HashSet<String>>,
nonces: HashSet<String>,
}
#[derive(Debug, Clone, Default)]
pub struct InMemoryPaidAddressStore {
inner: Arc<Mutex<Inner>>,
}
impl InMemoryPaidAddressStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn lock(&self) -> std::sync::MutexGuard<'_, Inner> {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
impl PaidAddressStore for InMemoryPaidAddressStore {
fn contains(&self, store_key: &str, address: &str) -> bool {
let key = normalize_address(address);
self.lock()
.paid
.get(store_key)
.is_some_and(|set| set.contains(&key))
}
fn record_success(&self, store_key: &str, address: &str) {
let key = normalize_address(address);
let mut inner = self.lock();
inner
.paid
.entry(store_key.to_owned())
.or_default()
.insert(key);
}
fn has_used_nonce(&self, nonce: &str) -> bool {
self.lock().nonces.contains(nonce)
}
fn record_nonce(&self, nonce: &str) {
let _ = self.lock().nonces.insert(nonce.to_owned());
}
fn consume_nonce(&self, nonce: &str) -> bool {
self.lock().nonces.insert(nonce.to_owned())
}
}
fn normalize_address(address: &str) -> String {
if address
.get(..2)
.is_some_and(|p| p.eq_ignore_ascii_case("0x"))
{
address.to_ascii_lowercase()
} else {
address.to_owned()
}
}