For DevelopersAPINonces & API Wallets

Nonces and API Wallets

This page covers the nonce model used for replay protection and the API wallet (agent) delegation system that enables automated trading without exposing your main private key.

Nonces

Every authenticated exchange action requires a nonce field. The nonce serves two purposes:

  1. Replay protection — prevents the same signed message from being submitted twice.
  2. Ordering guarantee — ensures actions are processed in the intended sequence.

Nonce Rules

  • The nonce must be the current Unix timestamp in milliseconds.
  • The server enforces a validity window: the nonce must be within 60 seconds of the server clock.
  • Each nonce may only be used once per address. Resubmitting a used nonce is rejected.
  • Nonces do not need to be strictly sequential, but they must be unique and within the time window.

Request Structure

{
  "action": { "type": "order", "orders": [...], "grouping": "na" },
  "nonce": 1700000000123,
  "signature": { "r": "0x...", "s": "0x...", "v": 27 }
}

Clock Synchronization

Ensure your system clock is synchronized via NTP. A skewed clock will cause nonce validation failures with an "Invalid signature" or "Nonce too old" error.

const nonce = Date.now(); // milliseconds
import time
nonce = int(time.time() * 1000)

Connection ID

The connectionId used in EIP-712 signing is derived from the nonce by zero-padding to 32 bytes:

import { ethers } from "ethers";
 
function nonceToConnectionId(nonce: number): string {
  return ethers.zeroPadValue(ethers.toBeHex(nonce), 32);
}

API Wallets (Agent Delegation)

An API wallet is a secondary Ethereum keypair authorized to sign exchange actions on behalf of your main address. This is the recommended approach for all automated trading systems.

Why Use API Wallets

BenefitDescription
Key isolationYour main wallet private key never touches the trading server
Instant revocationCompromised agents can be revoked immediately
Multi-agentRun multiple bots with separate agents simultaneously
On-chain transparencyAgent authorizations are recorded on-chain

Authorization Flow

Step 1: Generate an Agent Keypair

Create a new Ethereum keypair. This will be your agent wallet.

import { ethers } from "ethers";
 
const agentWallet = ethers.Wallet.createRandom();
console.log("Agent address:", agentWallet.address);
console.log("Agent private key:", agentWallet.privateKey);
// Store the private key securely -- it cannot be recovered
from eth_account import Account
 
agent = Account.create()
print(f"Agent address: {agent.address}")
print(f"Agent private key: {agent.key.hex()}")

Step 2: Authorize the Agent

Sign an approveAgent action with your main wallet to delegate signing authority to the agent:

POST /exchange
Content-Type: application/json
 
{
  "action": {
    "type": "approveAgent",
    "agentAddress": "0xAgentAddress...",
    "agentName": "My Trading Bot",
    "nonce": 1700000000000
  },
  "nonce": 1700000000000,
  "signature": {
    "r": "0x...",
    "s": "0x...",
    "v": 27
  }
}

The signature must be produced by your main wallet. Upon success, the agent is authorized to act on behalf of the main address.

Step 3: Trade with the Agent Key

All subsequent exchange actions can be signed with the agent’s private key. The API recognizes the agent and attributes actions to the main address.

import { GxClient } from "@gx-exchange/sdk";
 
const client = new GxClient();
await client.connect("https://api.gx.exchange");
 
// Use agent private key for signing
client.setWallet("0xAgentPrivateKey...");
const result = await client.placeOrder([{
  coin: "BTC",
  isBuy: true,
  sz: 0.001,
  limitPx: 60000,
  tif: "Gtc",
  reduceOnly: false,
}]);
from gx_exchange.exchange import Exchange
import eth_account
 
agent_account = eth_account.Account.from_key("0xAgentPrivateKey...")
exchange = Exchange(agent_account, "https://api.gx.exchange",
                    vault_address="0xMainWalletAddress...")
 
result = exchange.order("BTC", is_buy=True, sz=0.001, limit_px=60000,
                        order_type={"limit": {"tif": "Gtc"}})
print(f"Order status: {result['status']}")

Step 4: Revoke

To revoke an agent, submit another approveAgent action with the same agent address. Revocation is immediate — the agent can no longer sign any actions for the main address.

Querying Active Agents

POST /info
Content-Type: application/json

{ "type": "extraAgents", "user": "0xMainWalletAddress..." }

Response:

[
  {
    "name": "My Trading Bot",
    "address": "0xAgentAddress...",
    "validUntil": 0
  }
]

A validUntil of 0 means the agent has no expiry and remains active until explicitly revoked.

Security Recommendations

  1. Never share agent private keys. Treat them with the same care as any Ethereum private key.
  2. Use unique agents per deployment. Each bot or server should have its own agent keypair.
  3. Monitor agent activity. Periodically query extraAgents to verify only expected agents are authorized.
  4. Revoke unused agents immediately. Remove agents that are no longer in active use.
  5. Store keys in environment variables or a secrets manager. Never hardcode private keys in source code or commit them to version control.
  6. Rotate agents periodically. Generate new agent keypairs on a regular schedule and revoke old ones.