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:
- Replay Protection: Prevents malicious actors from re-submitting your signed requests
- 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: 1704067200000import time
def generate_nonce():
return int(time.time() * 1000) # Convert to milliseconds
# Example: 17040672000002. 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
| Issue | Cause | Solution |
|---|---|---|
| "Nonce already used" | System clock drift | Sync system time with NTP |
| "Nonce must be greater" | Nonce decreased | Ensure monotonic increase |
| Frequent conflicts | Multiple systems | Use distributed nonce strategy |
| Integer overflow | Nonce too large | Reset 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
- Examples - Complete working implementations with nonce management
- EIP-712 Signing - How nonces integrate with signatures
- Troubleshooting - Debug nonce-related issues
Related Documentation
- Authentication Overview - General authentication concepts
- Error Handling - Handle nonce errors properly