For the complete documentation index, see llms.txt. This page is also available as Markdown.

Authentication & Signing

For API key ownership, signer selection, and nonce rules, see API Keys and Nonces.

Typed signature

In Sodex, we use EIP712 for Typed structured data hashing and signing. We use different domains for the Add API key and other actions.

Add API key

For ordinary registration without builder or permissions, sign AddAPIKey. Set domain.chainId and X-API-Chain to the target network: mainnet (286623) or testnet (138565). The ordinary message has no chainID field; the EIP-712 domain provides network separation.

{
  types: {
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" }
    ],
    AddAPIKey: [
      { name: 'accountID', type: "uint64" },
      { name: 'name', type: 'string' },
      { name: 'keyType', type: 'uint8' },
      { name: 'publicKey', type: 'bytes' },
      { name: 'expiresAt', type: 'uint64' },
      { name: 'nonce', type: 'uint64' }
    ],
  },
  domain: {
    name: "universal",
    version: "1",
    chainId: <X-API-Chain>,
    verifyingContract: "0x0000000000000000000000000000000000000000"
  },
  primaryType: "AddAPIKey",
  message: {
    nonce: 1760373925000,
    accountID: 1010,
    name: "api-key-01",
    keyType: 1,
    publicKey: "0x3d4595c8742d0a58173a9963c05755b59a8f8256",
    expiresAt: 0,
  }
}

When the addAPIKey request includes builder, sign AddAPIKeyWithBuilder instead:

When creating a permissioned API key, sign UserSignedAddPermissionedAPIKeyAction. A permissioned API key request must omit builder.

Use the master wallet to sign all three registration types. For builder and permissioned registration, domain.chainId must match X-API-Chain, while message.chainID is 286623 for mainnet or 138565 for testnet. Ordinary AddAPIKey registration does not include message.chainID.

The REST request JSON and the EIP-712 typed data intentionally use different field names for the API key type. In request payloads this field is type, while in the typed signing structure it is keyType.

After you get the signature bytes sig, prepend byte 2 to get the typed signature. For example, your signed signature is 0x789a6bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab. The correct typed signature is 0x02789a6bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab.

Approve builder fee

Use the universal domain and sign ApproveBuilderFeeAction:

You must use the master wallet's private key to sign ApproveBuilderFeeAction. Like other universal actions, prepend byte 2 before the signature bytes.

maxFeeRate must be between 0 and 2000, inclusive. A value of 0 clears the builder approval.

Trading actions

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:

  • For spot: type is one of "newOrder", "cancelOrder", "newTwapOrder", "cancelTwapOrder", "transferAsset", "scheduleCancel", "revokeAPIKey", and more.

  • For perps: type is one of "newOrder", "cancelOrder", "newTwapOrder", "cancelTwapOrder", "updateLeverage", "updateMargin", "updateCollateral" (testnet only), "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 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:

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.

End-to-end signing example

Signing a newOrder action with a registered API key, end-to-end.

Prerequisite: you have already called addAPIKey (signed by the master wallet — see Add API Key) and hold the API key's private key locally.

Inputs

Steps

  1. Compute payloadHash as described in How to compute payloadHash.

  2. EIP-712-sign the ExchangeAction{payloadHash, nonce} struct with the API key's private key (0x2222…) under domain {name:"futures", chainId:286623, verifyingContract:0x00…00}.

  3. Prepend byte 0x01 to the 65-byte signature → this is the value of X-API-Sign.

  4. Send the request with X-API-Key set to the name of the API key:

The server looks up the API key named api-key-01 on accountID=12345, recovers the signer address from X-API-Sign, and verifies it equals the API key's registered publicKey (0x3d45…8256).

Common pitfalls

  • Putting the API key's EVM address (0x3d45…8256) in X-API-Keywrong; use the name string.

  • Signing with a key that does not match X-API-Key — when this header names a registered API key, sign with that key's private key. Direct master-wallet signing requires omitting X-API-Key; it is supported but not recommended for routine trading. Account-level actions such as API key registration, revocation, and builder-fee approval require the master wallet.

  • Forgetting the 0x01 prefix — the server rejects un-typed raw 65-byte signatures.

Signed Request Example (EIP-712)

Install viem and tsx, save as sign.ts, set SODEX_API_KEY_PRIVATE_KEY, and run npx tsx sign.ts. This example signs locally and does not submit an order. The account and order values below are examples; use your own registered account and market parameters before submission.

Last updated