nautechsystems/nautilus_trader · error

failed to allocate Lighter nonce: {e}

Error message

failed to allocate Lighter nonce: {e}

What it means

This error is raised by build_tx_context when the Lighter nonce allocator fails to hand out a nonce for a new transaction. Specifically it fires on the NonceError::SkipWindowExhausted branch: the skip window (used when the local nonce baseline is behind the venue) has been exhausted, usually because acks were lost and the local baseline is stale. The command fails immediately, but a background nonce-window recovery is spawned to resync the nonce from the venue so later commands can succeed.

Source

Thrown at crates/adapters/lighter/src/execution.rs:1485

        let nonce_guard = Arc::clone(&self.nonce_submission_gate)
            .try_read_owned()
            .context("Lighter nonce refresh is in progress")?;
        anyhow::ensure!(
            self.ws_client.connection_epoch() == connection_epoch
                && self.nonce_ready_connection_epoch.load(Ordering::Acquire) == connection_epoch,
            "Lighter connection or nonce state changed during transaction preparation",
        );
        let nonce = match self
            .dispatch
            .nonce_manager
            .next_nonce(credential.account_index(), credential.api_key_index())
        {
            Ok(nonce) => nonce,
            Err(e @ NonceError::SkipWindowExhausted { .. }) => {
                // Lost acks leave the baseline stale; resync from the venue so
                // later commands recover. The fetch is async; this command fails.
                self.spawn_nonce_window_recovery(credential);
                anyhow::bail!("failed to allocate Lighter nonce: {e}");
            }
            Err(e) => anyhow::bail!("failed to allocate Lighter nonce: {e}"),
        };

        let now_ns = self.clock.get_time_ns().as_u64() as i64;
        let expired_at = (now_ns / 1_000_000).saturating_add(DEFAULT_TX_EXPIRY_MS);
        let send_reservation = self
            .tx_send_sequencer
            .reserve(
                credential.account_index(),
                credential.api_key_index(),
                nonce,
            )
            .with_nonce_ownership(nonce_guard, connection_epoch);

        let context = TxContext {
            account_index: credential.account_index(),
            api_key_index: credential.api_key_index(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Let the automatically spawned nonce-window recovery complete, then re-issue the command — it resyncs the nonce from the venue
  2. Ensure only one process uses this Lighter API key / account at a time
  3. Restart the execution client to force a fresh nonce baseline fetch from the venue
  4. Check network stability so transaction acks are not lost between sends

Example fix

// before: immediate retry loop burning more nonces
for _ in 0..3 { client.update_leverage(...).await?; }
// after: await recovery, then retry once
if let Err(e) = client.update_leverage(...).await {
    if e.to_string().contains("failed to allocate Lighter nonce") {
        tokio::time::sleep(Duration::from_secs(2)).await; // allow nonce recovery to resync
        client.update_leverage(...).await?;
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// Rust: avoid issuing the command while nonce recovery is in flight
if !execution.nonce_recovery_in_progress() {
    execution.update_leverage(params).await?;
}

Try / catch

match execution.update_leverage(params).await {
    Err(e) if e.to_string().contains("failed to allocate Lighter nonce") => {
        // recovery was spawned server-side; wait then retry once
        tokio::time::sleep(Duration::from_secs(2)).await;
        execution.update_leverage(params).await?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling prepare_integrator_auto_approval, prepare_signed_modify_order, or update_leverage when the local nonce skip window is exhausted — i.e. the venue nonce has advanced past the locally tracked baseline faster than it was resynced (lost acks, reconnects, or another client sharing the same API key spending nonces).

Common situations: Running multiple trading instances against the same Lighter API key, WebSocket acks dropped during network instability, a client restart without nonce rehydration, or long-lived sessions where venue-side nonces drift from the adapter's counter.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/ca19390cfaa17857. Report an issue: GitHub.