> For the complete documentation index, see [llms.txt](https://sodex.com/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sodex.com/documentation/for-developers/developers/trading/spot-trading.md).

# Spot Trading

Use SoDEX Spot markets to exchange supported assets through the onchain order book.

## Integration flow

1. Query the supported symbols and coin metadata instead of hardcoding market identifiers.
2. Read tickers, the order book, recent trades, or candles to build the required market view.
3. Resolve the target `accountID` and confirm the available Spot balance.
4. Build the order request, sign it with a registered API key, and retain the returned order identifier.
5. Subscribe to account order and trade streams for realtime updates.
6. After reconnecting or receiving an ambiguous response, reconcile open orders and order history through REST.

Use decimal strings exactly as defined by the schema. Generate a stable client order ID when the request supports one so retries and reconciliation can identify the original intent.

## Signed order example with curl

This example places one testnet **limit buy order (GTC)** through the batch endpoint. Register an [API key](/documentation/for-developers/developers/trading/api-keys-and-nonces.md) and fund the Spot account with the quote asset before continuing. On Mainnet, fund via [ValueChain to Spot or Perps](/documentation/for-developers/developers/mirror-protocol/lifecycle/valuechain-transfers.md). To send Spot balances off-chain, use [Withdrawals](/documentation/for-developers/developers/mirror-protocol/lifecycle/withdrawals.md). Use the registered key's private key, not the main wallet's private key.

### 1. Prepare the account and order

Install Node.js 18+, `curl`, and `jq`, then install the signing dependencies in your example directory:

```bash
npm install viem tsx
export BASE_URL="https://testnet-gw.sodex.dev"
export USER_ADDRESS="<main-wallet-address>"
export SODEX_API_KEY_NAME="<registered-key-name>"

curl -sS "$BASE_URL/api/v1/spot/accounts/$USER_ADDRESS/state"
curl -sS "$BASE_URL/api/v1/spot/markets/symbols"
```

Set `ACCOUNT_ID` to the account state's `data.aid` and `SYMBOL_ID` to the selected market's `id`. Choose a price and quantity that satisfy that market's price, lot-size, and notional filters. Keep decimal values as strings.

```bash
export ACCOUNT_ID="<account-id>"
export SYMBOL_ID="<symbol-id>"
export ORDER_PRICE="<limit-price>"
export ORDER_QUANTITY="<base-asset-quantity>"
```

Provide `SODEX_API_KEY_PRIVATE_KEY` through your local secret manager or environment; do not put it in source code or shell history.

### 2. Sign the request locally

Save as `sign-spot-order.ts`. The script generates the body and authentication headers; it does not submit an order.

```typescript
import { concatHex, keccak256, toHex } from "viem";
import { privateKeyToAccount } from "viem/accounts";

async function main() {
  const accountID = Number(process.env.ACCOUNT_ID);
  const symbolID = Number(process.env.SYMBOL_ID);
  if (!Number.isSafeInteger(accountID) || !Number.isSafeInteger(symbolID)) {
    throw new Error("Set exact integer IDs; use an official SDK for IDs outside the safe integer range");
  }
  const price = process.env.ORDER_PRICE;
  const quantity = process.env.ORDER_QUANTITY;
  const keyName = process.env.SODEX_API_KEY_NAME;
  if (!price || !quantity || !keyName) {
    throw new Error("Set ORDER_PRICE, ORDER_QUANTITY, and SODEX_API_KEY_NAME");
  }
  const signer = privateKeyToAccount(
    process.env.SODEX_API_KEY_PRIVATE_KEY as `0x${string}`,
  );
  const nonce = BigInt(Date.now());
  const clOrdID = "spot-" + nonce.toString();
  const params = {
    accountID,
    orders: [{
      symbolID,
      clOrdID,
      side: 1, // BUY
      type: 1, // LIMIT
      timeInForce: 1, // GTC
      price,
      quantity,
    }],
  };
  const payloadHash = keccak256(
    toHex(JSON.stringify({ type: "batchNewOrder", params })),
  );
  const signature = await signer.signTypedData({
    domain: {
      name: "spot",
      version: "1",
      chainId: 138565,
      verifyingContract: "0x0000000000000000000000000000000000000000",
    },
    types: {
      ExchangeAction: [
        { name: "payloadHash", type: "bytes32" },
        { name: "nonce", type: "uint64" },
      ],
    },
    primaryType: "ExchangeAction",
    message: { payloadHash, nonce },
  });
  console.log(JSON.stringify({
    body: params,
    headers: {
      "X-API-Key": keyName,
      "X-API-Nonce": nonce.toString(),
      "X-API-Sign": concatHex(["0x01", signature]),
    },
  }));
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

The signed payload is `{type: "batchNewOrder", params}`; the HTTP body contains only `params`. Preserve the field order above and do not change the body after signing. SoDEX uses EIP-712, not HMAC. See [Manual REST Signing](/documentation/for-developers/developers/trading/authentication-and-signing/manual-rest-signing.md) for the signing rules.

### 3. Submit with curl

The following command **submits the order**. Use a dedicated API key for this process to avoid nonce contention, and submit immediately after signing. The generated file contains an executable signature; keep it private.

```bash
umask 077
npx tsx sign-spot-order.ts > spot-order.json &&
curl -sS -X POST "$BASE_URL/api/v1/spot/trade/orders/batch" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "X-API-Key: $(jq -r '.headers["X-API-Key"]' spot-order.json)" \
  -H "X-API-Nonce: $(jq -r '.headers["X-API-Nonce"]' spot-order.json)" \
  -H "X-API-Sign: $(jq -r '.headers["X-API-Sign"]' spot-order.json)" \
  --data-binary "$(jq -c '.body' spot-order.json)"
```

### 4. Check the result

Check the response's top-level `code` and each order's `data[].code`. For a successful order, match `clOrdID` and retain `orderID`. Acceptance does not mean the order has filled.

```bash
curl -sS "$BASE_URL/api/v1/spot/accounts/$USER_ADDRESS/orders"
curl -sS "$BASE_URL/api/v1/spot/accounts/$USER_ADDRESS/orders/history"
curl -sS "$BASE_URL/api/v1/spot/accounts/$USER_ADDRESS/trades"
```

Match the saved identifiers in these responses or use [account streams](/documentation/for-developers/api-reference/trading-api/websocket-v1.md) to follow execution. If submission times out, query the order before retrying; absence from open orders alone does not prove failure. Cancel an unfilled order with a newly signed [cancel request](/documentation/for-developers/api-reference/trading-api/rest-v1/sodex-rest-spot-api.md#cancel-multiple-orders).

## Documentation

{% content-ref url="/pages/haTOHzHJcT9bjlrCXIUQ" %}
[Trading Concepts](/documentation/for-developers/developers/trading/trading-mechanics.md)
{% endcontent-ref %}

{% content-ref url="/pages/o3Yj9kHutFbmg6uDLnhS" %}
[Authentication & Signing](/documentation/for-developers/developers/trading/authentication-and-signing.md)
{% endcontent-ref %}

{% content-ref url="/pages/HWeWqIzQUTNao2smQ2pp" %}
[Rate Limits](/documentation/for-developers/developers/trading/api-rate-limits.md)
{% endcontent-ref %}

## API Reference

{% content-ref url="/pages/aWS7JkCdqj5QJF6lF3wB" %}
[Spot](/documentation/for-developers/api-reference/trading-api/rest-v1/sodex-rest-spot-api.md)
{% endcontent-ref %}

{% content-ref url="/pages/U25Aeh8OoqsoN02ofoj4" %}
[WebSocket Streams](/documentation/for-developers/api-reference/trading-api/websocket-v1.md)
{% endcontent-ref %}
