Skip to content

Authentication Troubleshooting

This guide helps you diagnose and resolve common authentication issues when using the Synthetix API.

Quick Diagnosis

Authentication Checklist

Before diving into specific errors, verify these basics:

  • Private key is valid and has proper format
  • Domain separator matches exactly
  • Nonce is increasing and hasn't been used
  • Signature components are properly formatted
  • Network connectivity to the API endpoints
  • System time is synchronized (for nonce generation)

Common Error Messages

1. "Invalid signature"

Symptoms:
{
  "status": "error",
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid signature",
    "details": {}
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}
Possible Causes:

Wrong Domain Separator

// Wrong
const domain = {
  name: "SynthetixOffchain", // Wrong name
  version: "1",
  chainId: 1,
  verifyingContract: "0x1234567890123456789012345678901234567890" // Wrong address
};
 
// Correct
const domain = {
  name: "Synthetix", // Exact match required
  version: "1",
  chainId: 1,
  verifyingContract: "0x0000000000000000000000000000000000000000" // Zero address for off-chain
};

Incorrect Message Structure

// Wrong - order type as object
const message = {
  orders: [{
    symbol: "BTC-USDT",
    side: "buy",
    quantity: "0.1",
    price: "50000",
    orderType: "limitGtc", // String enum format
    triggerPrice: "",
    isTriggerMarket: false,
    reduceOnly: false
  }],
  nonce,
  expiresAfter
};
 
// Correct - order type as string
const message = {
  orders: [{
    symbol: "BTC-USDT",
    side: "buy",
    quantity: "0.1",
    price: "50000",
    orderType: "limitGtc", // Direct string enum
    triggerPrice: "",
    isTriggerMarket: false,
    reduceOnly: false
  }],
  nonce,
  expiresAfter
};

Chain ID Mismatch

// Check your target network
const chainIds = {
  mainnet: 1,
};
 
// Domain chainId must match your target
const domain = {
  name: "Synthetix",
  version: "1",
  chainId: 1, // Must match your environment
  verifyingContract: "0x0000000000000000000000000000000000000000"
};
Debug Steps:
  1. Verify Domain:
    console.log('Domain used:', JSON.stringify(domain, null, 2));
  2. Check Message Structure:
    console.log('Message structure:', JSON.stringify(message, null, 2));
  3. Validate Signature Components:
    function validateSignature(sig) {
      console.log('Signature v:', sig.v, '(should be 27 or 28)');
      console.log('Signature r length:', sig.r.length, '(should be 66)');
      console.log('Signature s length:', sig.s.length, '(should be 66)');
      console.log('Signature r format:', /^0x[0-9a-fA-F]{64}$/.test(sig.r));
      console.log('Signature s format:', /^0x[0-9a-fA-F]{64}$/.test(sig.s));
    }
  4. Test Signature Recovery:
    import { ethers } from 'ethers';
     
    // Verify signature locally
    const typedData = { domain, types, message };
    const digest = ethers.utils._TypedDataEncoder.hash(domain, types, message);
    const recoveredAddress = ethers.utils.recoverAddress(digest, signature);
     
    console.log('Expected address:', wallet.address);
    console.log('Recovered address:', recoveredAddress);
    console.log('Match:', recoveredAddress.toLowerCase() === wallet.address.toLowerCase());

2. "Nonce already used"

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

Concurrent Requests

// Use proper nonce management for concurrent requests
class ThreadSafeNonceManager {
  constructor() {
    this.lastNonce = 0;
    this.lock = false;
  }
 
  async generateNonce() {
    // Simple lock mechanism
    while (this.lock) {
      await new Promise(resolve => setTimeout(resolve, 10));
    }
 
    this.lock = true;
    const timestamp = Date.now();
    this.lastNonce = Math.max(timestamp, this.lastNonce + 1);
    const nonce = this.lastNonce;
    this.lock = false;
 
    return nonce;
  }
}

Multiple Systems with Same Key

// Use distributed nonce strategy
class DistributedNonceManager {
  constructor(systemId, totalSystems) {
    this.systemId = systemId;
    this.totalSystems = totalSystems;
  }
 
  generateNonce() {
    const baseTime = Date.now();
    // Each system gets different remainder when divided by total systems
    return baseTime * this.totalSystems + this.systemId;
  }
}
 
// System 0: nonces end in 0, 3, 6, 9...
// System 1: nonces end in 1, 4, 7...
// System 2: nonces end in 2, 5, 8...

3. "Insufficient margin"

Symptoms:
{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Insufficient margin",
    "details": {
      "requiredMargin": "5000.00",
      "availableMargin": "1000.00"
    }
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}
Solutions:

Check Account Balance First

async function checkMarginBeforeOrder(client, order) {
  // Get account info (implement based on your client)
  const accountInfo = await client.getAccountInfo();
  const availableMargin = parseFloat(accountInfo.availableMargin);
 
  // Estimate required margin for order
  const orderValue = parseFloat(order.price) * parseFloat(order.quantity);
  const estimatedMargin = orderValue * 0.1; // Rough 10x leverage estimate
 
  if (availableMargin < estimatedMargin) {
    throw new Error(`Insufficient margin: need ${estimatedMargin}, have ${availableMargin}`);
  }
 
  return true;
}

Self-Help Resources

Community Support

  • Discord: Join the Synthetix developer community