# Overview

## Endpoint

* Mainnet REST Endpoint
  * Spot: `https://mainnet-gw.sodex.dev/api/v1/spot`
  * Perps: `https://mainnet-gw.sodex.dev/api/v1/perps`
* Mainnet WebSocket Endpoint
  * Spot: `wss://mainnet-gw.sodex.dev/ws/spot`
  * Perps: `wss://mainnet-gw.sodex.dev/ws/perps`
* Testnet REST Endpoint
  * Spot: `https://testnet-gw.sodex.dev/api/v1/spot`
  * Perps: `https://testnet-gw.sodex.dev/api/v1/perps`
* Testnet WebSocket Endpoint
  * Spot: `wss://testnet-gw.sodex.dev/ws/spot`
  * Perps: `wss://testnet-gw.sodex.dev/ws/perps`

## API keys and nonces

### API keys

A master account can approve or revoke API keys to sign on behalf of the master account or any of the sub-accounts. Each master account can have at most **5** API keys.

In Sodex, we currently support EVM address as the API key. Users can sign the transaction with their EVM private key. Note that API keys are only used to sign. To query the account data associated with a master or sub-account, you must pass in the actual account ID of that account.

### Sodex nonces

Similar to Hyperliquid, on Sodex, the **`100`** highest nonces are stored per address. Every new transaction must have nonce larger than the smallest nonce in this set and also never have been used before. Nonces are tracked per API key, which is the user address if signed with private key of the address, or the agent address if signed with an API wallet.

Nonces must be within `(T - 2 days, T + 1 day)`, where `T` is the Unix millisecond timestamp on the block of the transaction.

The following steps may help port over an automated strategy from a centralized exchange:

1. Use an API wallet per trading process. Note that nonces are stored per API key (i.e. private key), so separate sub-accounts signed by the same API wallet will share the nonce tracker of the API wallet. It's recommended to use separate API wallets for different sub-accounts.
2. The trading logic tasks send orders and cancels to the batching task.
3. For each batch of orders or cancels, fetch and increment an atomic counter that ensures a unique nonce for the address. The atomic counter can be fast-forwarded to current Unix milliseconds if needed.

This structure is robust to out-of-order transactions within `2` seconds, which should be sufficient for an automated strategy geographically near an API server.

### Typed signature

In Sodex, we use [EIP712](https://eips.ethereum.org/EIPS/eip-712) for Typed structured data hashing and signing. We use different domains for different actions.

#### Other actions

```typescript
{
  types: {
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" }
    ],
    ExchangeAction: [
      { name: 'payloadHash', type: "bytes32" },
      { name: 'nonce', type: 'uint64' }
    ],
  },
  domain: {
    name: "spot" or "futures",
    version: "1",
    chainId: 286623 or 138565,
    verifyingContract: "0x0000000000000000000000000000000000000000"
  },
  primaryType: "ExchangeAction",
  message: {
    payloadHash: "0x7521d1cadbcfa91eec65aa16715b94ffc1c9654ba57ea2ef1a2127bca1127a83",
    nonce: 1760373925000,
  }
}
```

For mainnet use `286623` for `domain.chainId`, for testnet use `138565` for `domain.chainId`. For spot actions, use `spot` for `domain.name` and for perps actions, `futures` for `domain.name`.

After you get the signature bytes `sig`, append byte `1` before the sig bytes to get typed signature. For example, your signed signature is `0x789a6bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab`, the correct typed signature is `0x01789a6bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab`.

#### How to compute `payloadHash`

`payloadHash = Keccak256(json.Marshal(payload))`

The payload is a JSON object with two fields:

```typescript
{
  "type": "<actionName>",   // e.g. "newOrder", "cancelOrder", "updateLeverage"
  "params": { ... }         // action-specific parameters
}
```

* For spot: `type` is one of `"newOrder"`, `"cancelOrder"`, `"transferAsset"`, `"scheduleCancel"`, `"revokeAPIKey"`, and more.
* For perps: `type` is one of `"newOrder"`, `"cancelOrder"`, `"updateLeverage"`, `"updateMargin"`, `"transferAsset"`, `"scheduleCancel"`, `"revokeAPIKey"`, and more.

You must use your private key of the master account to sign `revokeAPIKey` action.

**Important rules for producing a correct `payloadHash`:**

1. **Compact JSON** — no whitespace or newlines. Use `json.Marshal` in Go, or `JSON.stringify` without extra arguments in JavaScript.
2. **Key order must match the Go struct field order** — the server verifies signatures by parsing the request body into Go structs and re-marshaling via `json.Marshal`, which serializes fields in struct definition order. If your JSON keys are in a different order, the hash will differ and signature verification will fail.
   * Refer to the struct definitions in [sodex-go-sdk-public](https://github.com/sodex-tech/sodex-go-sdk-public) for the authoritative field order.
   * For example, `PerpsOrderItem` fields must appear in this order: `clOrdID`, `modifier`, `side`, `type`, `timeInForce`, `price`, `quantity`, `funds`, `stopPrice`, `stopType`, `triggerType`, `reduceOnly`, `positionSide`.
3. **`DecimalString` fields are JSON strings, not numbers** — fields typed as `DecimalString` in the schema (e.g. `price`, `quantity`, `funds`, `stopPrice`) must be serialized as quoted strings in the signing payload (e.g. `"quantity":"0.001"`, not `"quantity":0.001`). The HTTP request body uses the same format.
4. **`omitempty` fields must be omitted when unset** — optional pointer fields in the Go struct (those with `json:",omitempty"`) must not appear in the JSON when they have no value. Non-optional fields (e.g. `modifier`, `reduceOnly`, `positionSide`) must always be present, even with zero values.

**Example** — perps market buy order signing payload:

```json
{"type":"newOrder","params":{"accountID":12345,"symbolID":1,"orders":[{"clOrdID":"my-order-1","modifier":1,"side":1,"type":2,"timeInForce":3,"quantity":"0.001","reduceOnly":false,"positionSide":1}]}}
```

Note: the HTTP request body contains only the `params` object (without the `type` wrapper), using the same field order and types as the signing payload.

## Reference Pages

{% content-ref url="go-sdk-signing-guide" %}
[go-sdk-signing-guide](https://sodex.com/documentation/api/go-sdk-signing-guide)
{% endcontent-ref %}

{% content-ref url="api-rate-limits" %}
[api-rate-limits](https://sodex.com/documentation/api/api-rate-limits)
{% endcontent-ref %}

{% content-ref url="rest-v1" %}
[rest-v1](https://sodex.com/documentation/api/rest-v1)
{% endcontent-ref %}

{% content-ref url="websocket-v1" %}
[websocket-v1](https://sodex.com/documentation/api/websocket-v1)
{% endcontent-ref %}
