r402_extensions/siwx/
paid.rs1use std::collections::{HashMap, HashSet};
13use std::sync::{Arc, Mutex};
14
15pub trait PaidAddressStore: Send + Sync {
17 fn contains(&self, store_key: &str, address: &str) -> bool;
19
20 fn record_success(&self, store_key: &str, address: &str);
22
23 fn has_used_nonce(&self, nonce: &str) -> bool;
25
26 fn record_nonce(&self, nonce: &str);
28
29 #[must_use]
34 fn consume_nonce(&self, nonce: &str) -> bool;
35}
36
37#[derive(Debug, Default)]
38struct Inner {
39 paid: HashMap<String, HashSet<String>>,
41 nonces: HashSet<String>,
42}
43
44#[derive(Debug, Clone, Default)]
49pub struct InMemoryPaidAddressStore {
50 inner: Arc<Mutex<Inner>>,
51}
52
53impl InMemoryPaidAddressStore {
54 #[must_use]
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 fn lock(&self) -> std::sync::MutexGuard<'_, Inner> {
61 self.inner
62 .lock()
63 .unwrap_or_else(std::sync::PoisonError::into_inner)
64 }
65}
66
67impl PaidAddressStore for InMemoryPaidAddressStore {
68 fn contains(&self, store_key: &str, address: &str) -> bool {
69 let key = normalize_address(address);
70 self.lock()
71 .paid
72 .get(store_key)
73 .is_some_and(|set| set.contains(&key))
74 }
75
76 fn record_success(&self, store_key: &str, address: &str) {
77 let key = normalize_address(address);
78 let mut inner = self.lock();
79 inner
80 .paid
81 .entry(store_key.to_owned())
82 .or_default()
83 .insert(key);
84 }
85
86 fn has_used_nonce(&self, nonce: &str) -> bool {
87 self.lock().nonces.contains(nonce)
88 }
89
90 fn record_nonce(&self, nonce: &str) {
91 let _ = self.lock().nonces.insert(nonce.to_owned());
92 }
93
94 fn consume_nonce(&self, nonce: &str) -> bool {
95 self.lock().nonces.insert(nonce.to_owned())
96 }
97}
98
99fn normalize_address(address: &str) -> String {
101 if address
102 .get(..2)
103 .is_some_and(|p| p.eq_ignore_ascii_case("0x"))
104 {
105 address.to_ascii_lowercase()
106 } else {
107 address.to_owned()
108 }
109}