Skip to content

Nonce Management

The nonce system is a critical security feature that prevents replay attacks and ensures request uniqueness in the Synthetix APIs. Every authenticated request must include a unique, incrementing nonce.

Overview

A nonce (number used once) serves two purposes:

  1. Replay Protection: Prevents malicious actors from re-submitting your signed requests
  2. Request Ordering: Ensures requests are processed in the intended order

Nonce Requirements

Format

  • Must be a positive integer
  • Recommended: Use Unix timestamp in milliseconds (13 digits)
  • Must be strictly increasing (each nonce > previous nonce)
  • Maximum value: 9223372036854775807 (max safe integer)

Example

{
  "action": {
    "action": "placeOrders",
    "order": { ... }
  },
  "nonce": 1704067200000,  // Unix timestamp in milliseconds
  "signature": { ... }
}

Implementation Notes

1. Use Timestamp as Nonce

The recommended approach is using the current timestamp in milliseconds:

function generateNonce() {
  return Date.now();  // Returns timestamp in milliseconds
}
 
// Example: 1704067200000
import time
 
def generate_nonce():
    return int(time.time() * 1000)  # Convert to milliseconds
 
# Example: 1704067200000

2. Handle Nonce Conflicts

If multiple requests are sent within the same millisecond, increment the nonce:

class NonceManager {
  constructor() {
    this.lastNonce = 0;
  }
 
  generateNonce() {
    const timestamp = Date.now();
    // Ensure nonce is always increasing
    this.lastNonce = Math.max(timestamp, this.lastNonce + 1);
    return this.lastNonce;
  }
}
 
const nonceManager = new NonceManager();
const nonce1 = nonceManager.generateNonce(); // 1704067200000
const nonce2 = nonceManager.generateNonce(); // 1704067200001 (incremented)

3. Synchronized Nonce Across Systems

When running multiple trading systems with the same account:

class DistributedNonceManager {
  constructor(systemId, totalSystems) {
    this.systemId = systemId;      // 0, 1, 2, etc.
    this.totalSystems = totalSystems;
    this.counter = 0;
  }
 
  generateNonce() {
    // Each system uses different milliseconds
    const baseTime = Date.now();
    const offset = this.systemId;
 
    // Ensure uniqueness across systems
    return baseTime * this.totalSystems + offset;
  }
}
 
// System 1: nonces end in 0: 17040672000000, 17040672000010, ...
// System 2: nonces end in 1: 17040672000001, 17040672000011, ...

Error Handling

Nonce Already Used Error

When a nonce has already been used:

{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Nonce already used",
    "details": {
      "lastNonce": 1704067200000,
      "attemptedNonce": 1704067199000
    }
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}

Recovery Strategy

async function executeWithNonceRetry(action, maxRetries = 3) {
  const nonceManager = new NonceManager();
 
  for (let i = 0; i < maxRetries; i++) {
    try {
      const nonce = nonceManager.generateNonce();
      const response = await sendRequest({
        action,
        nonce,
        signature: signRequest(action, nonce)
      });
      return response;
    } catch (error) {
      if (error.message.includes('Nonce already used') && i < maxRetries - 1) {
        // Force increment for next attempt
        nonceManager.lastNonce += 1000;
        continue;
      }
      throw error;
    }
  }
}

Advanced Nonce Strategies

1. High-Frequency Trading

For HFT systems sending hundreds of requests per second:

class HFTNonceManager {
  constructor() {
    this.baseTime = Date.now();
    this.counter = 0;
  }
 
  generateNonce() {
    // Combine timestamp with microsecond counter
    const elapsed = Date.now() - this.baseTime;
    this.counter++;
 
    // Format: [timestamp][counter]
    // Ensures uniqueness even within same millisecond
    return parseInt(`${Date.now()}${String(this.counter).padStart(6, '0')}`);
  }
 
  reset() {
    // Reset counter periodically to prevent overflow
    if (this.counter > 999999) {
      this.baseTime = Date.now();
      this.counter = 0;
    }
  }
}

2. Batch Operations

When sending batch operations, use sequential nonces:

function generateBatchNonces(batchSize) {
  const baseNonce = Date.now();
  const nonces = [];
 
  for (let i = 0; i < batchSize; i++) {
    nonces.push(baseNonce + i);
  }
 
  return nonces;
}
 
// Example: Placing 3 orders in parallel
const nonces = generateBatchNonces(3);
// [1704067200000, 1704067200001, 1704067200002]

3. Fallback Nonce Sources

Multiple nonce generation strategies for reliability:

class RobustNonceManager {
  constructor() {
    this.lastNonce = 0;
  }
 
  generateNonce() {
    const strategies = [
      () => Date.now(),
      () => Date.now() * 1000 + Math.floor(Math.random() * 1000),
      () => this.lastNonce + 1000,
      () => BigInt(Date.now()) * 1000000n + BigInt(process.hrtime()[1])
    ];
 
    for (const strategy of strategies) {
      try {
        const nonce = Number(strategy());
        if (nonce > this.lastNonce) {
          this.lastNonce = nonce;
          return nonce;
        }
      } catch (e) {
        continue;
      }
    }
 
    // Ultimate fallback
    this.lastNonce += 1;
    return this.lastNonce;
  }
}

Monitoring and Debugging

Nonce Tracking

Keep track of nonces for debugging:

class DebugNonceManager {
  constructor() {
    this.lastNonce = 0;
    this.nonceHistory = [];
  }
 
  generateNonce() {
    const nonce = Date.now();
    this.lastNonce = Math.max(nonce, this.lastNonce + 1);
 
    // Track for debugging
    this.nonceHistory.push({
      nonce: this.lastNonce,
      timestamp: new Date().toISOString(),
      stack: new Error().stack
    });
 
    // Keep only last 100 for memory efficiency
    if (this.nonceHistory.length > 100) {
      this.nonceHistory.shift();
    }
 
    return this.lastNonce;
  }
 
  getHistory() {
    return this.nonceHistory;
  }
}

Common Issues and Solutions

IssueCauseSolution
"Nonce already used"System clock driftSync system time with NTP
"Nonce must be greater"Nonce decreasedEnsure monotonic increase
Frequent conflictsMultiple systemsUse distributed nonce strategy
Integer overflowNonce too largeReset or use modulo arithmetic

Security

  • Never Reuse Nonces: Each nonce must be unique per account
  • Don't Use Predictable Patterns: While timestamps are recommended, add randomness for sensitive operations
  • Validate Locally: Check nonce increment before sending to avoid unnecessary API calls
  • Secure Storage: If persisting last nonce, store securely to prevent tampering

Implementation Examples

Full TypeScript Example

interface NonceManager {
  generateNonce(): bigint;
}
 
class ProductionNonceManager implements NonceManager {
  private lastNonce: bigint = 0n;
  private readonly maxNonce = BigInt(Number.MAX_SAFE_INTEGER);
 
  generateNonce(): bigint {
    const timestamp = BigInt(Date.now());
 
    // Ensure always increasing
    this.lastNonce = timestamp > this.lastNonce ? timestamp : this.lastNonce + 1n;
 
    // Check bounds
    if (this.lastNonce > this.maxNonce) {
      throw new Error('Nonce overflow - reset required');
    }
 
    return this.lastNonce;
  }
 
  reset(): void {
    this.lastNonce = 0n;
  }
}

Python Example

import time
import threading
from typing import Optional
 
class NonceManager:
    def __init__(self):
        self.last_nonce: int = 0
        self.lock = threading.Lock()
 
    def generate_nonce(self) -> int:
        with self.lock:
            timestamp = int(time.time() * 1000)
            self.last_nonce = max(timestamp, self.last_nonce + 1)
            return self.last_nonce
 
    def reset(self) -> None:
        with self.lock:
            self.last_nonce = 0
 
# Global instance for thread safety
nonce_manager = NonceManager()

Testing Nonce Management

Always test your nonce management system:

// Test monotonic increase
const manager = new NonceManager();
const nonces = Array(1000).fill(0).map(() => manager.generateNonce());
const isMonotonic = nonces.every((n, i) => i === 0 || n > nonces[i-1]);
console.assert(isMonotonic, 'Nonces must be strictly increasing');
 
// Test uniqueness
const uniqueNonces = new Set(nonces);
console.assert(uniqueNonces.size === nonces.length, 'All nonces must be unique');

Integration with EIP-712 Signing

When using nonces with EIP-712 signatures:

import { SynthetixSigner } from './eip-712-signing';
 
class AuthenticatedRequestManager {
  constructor(privateKey) {
    this.signer = new SynthetixSigner(privateKey);
    this.nonceManager = new NonceManager();
  }
 
  async placeOrders(orders, expiresAfter) {
    const nonce = this.nonceManager.generateNonce();
    const signature = await this.signer.signPlaceOrders(orders, nonce, expiresAfter);
 
    return {
      action: {
        type: "placeOrders",
        orders
      },
      nonce,
      signature,
      expiresAfter
    };
  }
}

Next Steps

Related Documentation