acta-maker-sdk 0.8.2

Rust SDK for the Acta options RFQ venue on Solana: maker and vault-operator WebSocket clients, order signing, chain builders
Documentation
use rand::rngs::{OsRng, StdRng};
use rand::{RngCore, SeedableRng, TryRngCore};
use std::sync::atomic::{AtomicU64, Ordering};

#[derive(Debug)]
pub struct NonceError(String);

impl std::fmt::Display for NonceError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl std::error::Error for NonceError {}

pub struct NonceGenerator {
    rng: StdRng,
}

impl NonceGenerator {
    pub fn new() -> Result<Self, NonceError> {
        let mut seed = [0u8; 32];
        let mut rng = OsRng;
        rng.try_fill_bytes(&mut seed)
            .map_err(|e| NonceError(format!("OsRng unavailable: {e}")))?;
        Ok(Self {
            rng: StdRng::from_seed(seed),
        })
    }

    pub fn next_u64(&mut self) -> u64 {
        self.rng.next_u64()
    }
}

/// Atomic counter offset by a lazily initialized OS-entropy seed.
/// Counter arithmetic wraps modulo 2^64.
/// Call [`Self::init`] to check entropy availability before the first nonce.
pub struct AtomicNonceGenerator {
    counter: AtomicU64,
    entropy_base: AtomicU64,
}

impl AtomicNonceGenerator {
    pub const fn new() -> Self {
        Self {
            counter: AtomicU64::new(1),
            entropy_base: AtomicU64::new(0),
        }
    }

    pub fn init(&self) -> Result<(), NonceError> {
        if self.entropy_base.load(Ordering::Relaxed) == 0 {
            let mut buf = [0u8; 8];
            let mut rng = OsRng;
            rng.try_fill_bytes(&mut buf)
                .map_err(|e| NonceError(format!("OsRng unavailable: {e}")))?;
            let entropy = u64::from_le_bytes(buf);
            let _ =
                self.entropy_base
                    .compare_exchange(0, entropy, Ordering::SeqCst, Ordering::Relaxed);
        }
        Ok(())
    }

    #[inline]
    pub fn next_u64(&self) -> Result<u64, NonceError> {
        let base = self.entropy_base.load(Ordering::Relaxed);
        let base = if base == 0 {
            self.init()?;
            self.entropy_base.load(Ordering::Relaxed)
        } else {
            base
        };

        let count = self.counter.fetch_add(1, Ordering::Relaxed);
        Ok(base.wrapping_add(count))
    }
}

impl Default for AtomicNonceGenerator {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
#[path = "nonce_tests.rs"]
mod tests;