Skip to main content

Eip3009NonceStore

Trait Eip3009NonceStore 

Source
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_fresh is 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 observe Fresh.
  • Any present entry is treated as a replay; the record path never prunes. gc_expired is the only entry point that drops entries, so replay decisions stay decoupled from the wall clock.

Required Methods§

Source

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.

Source

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.

Source

fn len(&self) -> Result<usize, SettlementError>

Number of currently retained entries. Used by tests and metrics.

Provided Methods§

Source

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".

Implementors§