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:
typeis one of"newOrder","cancelOrder","newTwapOrder","cancelTwapOrder","transferAsset","scheduleCancel","revokeAPIKey", and more.For perps:
typeis 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:
Compact JSON — no whitespace or newlines. Use
json.Marshalin Go, orJSON.stringifywithout extra arguments in JavaScript.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,
PerpsOrderItemfields must appear in this order:clOrdID,modifier,side,type,timeInForce,price,quantity,funds,stopPrice,stopType,triggerType,reduceOnly,positionSide.
DecimalStringfields are JSON strings, not numbers — fields typed asDecimalStringin 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.omitemptyfields must be omitted when unset — optional pointer fields in the Go struct (those withjson:",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
Compute
payloadHashas described in How to computepayloadHash.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}.Prepend byte
0x01to the 65-byte signature → this is the value ofX-API-Sign.Send the request with
X-API-Keyset 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) inX-API-Key— wrong; use thenamestring.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 omittingX-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
0x01prefix — 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