> 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/perp-trading.md).

# Perp Trading

Use SoDEX perpetual markets for leveraged trading with no expiry date.

## Integration flow

1. Query supported symbols, coin metadata, tickers, mark prices, and the order book.
2. Resolve the target `accountID`, then load balances, open positions, open orders, and the current fee rate.
3. Configure leverage or isolated margin when required before placing the dependent order.
4. Build and sign the order with a registered API key, then retain the returned order identifier.
5. Subscribe to mark-price, account, position, order, and trade streams for realtime updates.
6. Reconcile positions and order history through REST after reconnecting or receiving an ambiguous response.

Risk calculations should use the current mark price and the account state returned by SoDEX. Do not infer a completed position change from order acceptance alone.

## Signed order example with curl

This example places one testnet **limit buy order (GTC)**. Register an [API key](/documentation/for-developers/developers/trading/api-keys-and-nonces.md) and fund the Perps account with sufficient margin before continuing. On Mainnet, fund via [ValueChain to Spot or Perps](/documentation/for-developers/developers/mirror-protocol/lifecycle/valuechain-transfers.md). To send Perps 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/perps/accounts/$USER_ADDRESS/state"
curl -sS "$BASE_URL/api/v1/perps/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. This example uses the account's current leverage and margin settings.

```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-perps-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 = "perps-" + nonce.toString();
  const params = {
    accountID,
    symbolID,
    orders: [{
      clOrdID,
      modifier: 1, // NORMAL
      side: 1, // BUY
      type: 1, // LIMIT
      timeInForce: 1, // GTC
      price,
      quantity,
      reduceOnly: false,
      positionSide: 1, // BOTH
    }],
  };
  const payloadHash = keccak256(
    toHex(JSON.stringify({ type: "newOrder", params })),
  );
  const signature = await signer.signTypedData({
    domain: {
      name: "futures",
      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: "newOrder", 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-perps-order.ts > perps-order.json &&
curl -sS -X POST "$BASE_URL/api/v1/perps/trade/orders" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "X-API-Key: $(jq -r '.headers["X-API-Key"]' perps-order.json)" \
  -H "X-API-Nonce: $(jq -r '.headers["X-API-Nonce"]' perps-order.json)" \
  -H "X-API-Sign: $(jq -r '.headers["X-API-Sign"]' perps-order.json)" \
  --data-binary "$(jq -c '.body' perps-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/perps/accounts/$USER_ADDRESS/orders"
curl -sS "$BASE_URL/api/v1/perps/accounts/$USER_ADDRESS/orders/history"
curl -sS "$BASE_URL/api/v1/perps/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-perps-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/7tsxcsH83bWvc6HxmvND" %}
[Perps](/documentation/for-developers/api-reference/trading-api/rest-v1/sodex-rest-perps-api.md)
{% endcontent-ref %}

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