pub trait Eip3009NonceStore: Send + Sync {
// Required methods
fn record_if_fresh(
&self,
from_address: &str,
nonce: &str,
retain_until_unix_seconds: u64,
) -> Result<NonceOutcome, SettlementError>;
fn gc_expired(
&self,
now_unix_seconds: u64,
) -> Result<usize, SettlementError>;
fn len(&self) -> Result<usize, SettlementError>;
// Provided method
fn is_empty(&self) -> Result<bool, SettlementError> { ... }
}Expand description
Single-use nonce store for EIP-3009 authorization replay resistance.
This is the trust-boundary surface settlement consults BEFORE
preparing a transfer. The store is keyed on the EIP-3009 authorizer
(from address) and a nonce key that scopes the 32-byte authorization
nonce to its EIP-712 domain (chain id + verifying contract): per
EIP-3009 the (from, nonce) pair is consumed on-chain by a SPECIFIC
token contract, so the same nonce value is an independent spend on a
different token or chain. Recording the domain-scoped pair here makes
the off-chain preparation path single-use per token contract as well,
closing the replay window before a signature is ever broadcast without
rejecting a legitimate reuse of the same nonce on a different contract.
prepare_transfer_with_authorization keys the store on the canonical
0x lowercase hex of the PARSED from/nonce bytes (with the chain id
and verifying contract folded into the nonce key), so a re-prefixed or
re-cased submission of the same authorization on the same domain maps to
the same entry. Implementations additionally lowercase the supplied key
defensively, so direct callers cannot evade detection through casing
either.
Implementations are Send + Sync so callers can hold them in an
Arc<dyn Eip3009NonceStore>. The contract intentionally mirrors
chio_custody_hw::nonce_store::PasskeyNonceStore:
record_if_freshis the only mutating entry point and MUST be atomic with respect to concurrent calls, so two parallel replays of the same(from, nonce)cannot both observeFresh.- Any present entry is treated as a replay; the record path never
prunes.
gc_expiredis the only entry point that drops entries, so replay decisions stay decoupled from the wall clock.
Required Methods§
Sourcefn record_if_fresh(
&self,
from_address: &str,
nonce: &str,
retain_until_unix_seconds: u64,
) -> Result<NonceOutcome, SettlementError>
fn record_if_fresh( &self, from_address: &str, nonce: &str, retain_until_unix_seconds: u64, ) -> Result<NonceOutcome, SettlementError>
Record (from_address, nonce) for replay detection.
retain_until_unix_seconds is the time the entry stays GC-able
until (typically the authorization valid_before). Atomicity:
two concurrent calls with the same key cannot both observe
NonceOutcome::Fresh.
Sourcefn gc_expired(&self, now_unix_seconds: u64) -> Result<usize, SettlementError>
fn gc_expired(&self, now_unix_seconds: u64) -> Result<usize, SettlementError>
Sweep entries whose retention bound is below now_unix_seconds.
Returns the number of records pruned. Advisory: failing to run it
never causes a false NonceOutcome::Fresh.
Sourcefn len(&self) -> Result<usize, SettlementError>
fn len(&self) -> Result<usize, SettlementError>
Number of currently retained entries. Used by tests and metrics.
Provided Methods§
Sourcefn is_empty(&self) -> Result<bool, SettlementError>
fn is_empty(&self) -> Result<bool, SettlementError>
True if no entries are retained.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".