> ## Documentation Index
> Fetch the complete documentation index at: https://docs.foresight.now/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing orders

> Build and sign EIP-712 orders for the CLOB: the typed-data domain, the Order struct, the integer-tick maker/taker amount math, and a complete worked example.

Orders are signed off-chain with EIP-712 typed data and submitted to
`POST /v1/orders`. This page covers exactly what to sign and how to compute the
amounts so the matching engine accepts your order.

<Warning>
  Amount math is unforgiving. `maker_amount` and `taker_amount` must be derived
  with **BigInt integer-tick math** at the collateral's decimals (18 on BSC),
  with **BUY ceiling** and **SELL flooring** the collateral. Anything signed with
  float arithmetic or the wrong decimals is rejected at ingest with
  `Drifted signed amounts`.
</Warning>

## EIP-712 domain

```ts theme={null}
const domain = {
  name: "ForesightExchange",
  version: "1",
  chainId: 56,                              // BSC mainnet
  verifyingContract: market.ctf_exchange_address, // from GET /v1/markets/{id}
};
```

`verifyingContract` is the market's **`ctf_exchange_address`** — read it from the
market object. Do not hard-code it; it can differ per chain.

## The `Order` struct

The typed-data primary type is `Order`, with these 12 fields **in this order**:

```ts theme={null}
const types = {
  Order: [
    { name: "salt",          type: "uint256" },
    { name: "maker",         type: "address" },
    { name: "signer",        type: "address" },
    { name: "taker",         type: "address" },
    { name: "tokenId",       type: "uint256" },
    { name: "makerAmount",   type: "uint256" },
    { name: "takerAmount",   type: "uint256" },
    { name: "expiration",    type: "uint256" },
    { name: "nonce",         type: "uint256" },
    { name: "feeRateBps",    type: "uint256" },
    { name: "side",          type: "uint8"   },
    { name: "signatureType", type: "uint8"   },
  ],
};
```

| Field           | Meaning                                                            |
| --------------- | ------------------------------------------------------------------ |
| `salt`          | Random `uint256` (as a decimal string) for uniqueness              |
| `maker`         | The wallet placing the order. **Must equal `signer`.**             |
| `signer`        | The signing wallet. Must equal the authenticated wallet.           |
| `taker`         | Counterparty restriction. Use the zero address for an open order.  |
| `tokenId`       | ERC-1155 `token_id` of the outcome you're trading (from `/tokens`) |
| `makerAmount`   | What you give, in raw token wei. See [amount math](#amount-math).  |
| `takerAmount`   | What you receive, in raw token wei                                 |
| `expiration`    | Unix seconds; `0` = no expiry. Required non-zero for `GTD` orders. |
| `nonce`         | Order nonce                                                        |
| `feeRateBps`    | Fee in basis points (`100` = 1%)                                   |
| `side`          | `0` = BUY, `1` = SELL                                              |
| `signatureType` | `0` = EOA, `1` = EIP-1271 (smart-contract wallet)                  |

<Info>
  The order **kind** (`LIMIT` / `MARKET` / `GTD`) is **not** part of the signed
  struct — it travels on the REST request body only as `order_type`. The signed
  struct carries `side` as a number, not the human-readable string.
</Info>

## Outcome → token

Pick the `token_id` for the outcome you want from
`GET /v1/markets/{condition_id}/tokens`:

```json theme={null}
{
  "tokens": [
    { "outcome": 0, "outcome_label": "NO",  "token_id": "1234...", "decimals": 18 },
    { "outcome": 1, "outcome_label": "YES", "token_id": "5678...", "decimals": 18 }
  ]
}
```

To buy YES, sign with `outcome: 1` and the YES `token_id`.

## Prices are 2-decimal ticks

A price is a YES probability in `[0.01, 1.00]`, quantized to **2 decimal places**
(100 ticks). Sign at sub-tick precision and the on-chain crossing check can
reject the match, so round first:

```ts theme={null}
// "0.7657" -> "0.77"  (round half up to 2dp)
function quantizePrice(price: string): string {
  const [w = "0", f = ""] = price.split(".");
  if (f.length <= 2) return `${BigInt(w)}.${(f + "00").slice(0, 2)}`;
  let ticks = BigInt(w) * 100n + BigInt(f.slice(0, 2));
  if (f[2] >= "5") ticks += 1n;
  return `${ticks / 100n}.${(ticks % 100n).toString().padStart(2, "0")}`;
}
```

## Amount math

Read `decimals` from the market's `/tokens` response (**18 on BSC**). Then, with
`price` (already quantized) and `size` as decimal strings:

```
BUY:   makerAmount = ceil(size × price)   (collateral you pay)
       takerAmount = size                 (shares you receive)

SELL:  makerAmount = size                 (shares you sell)
       takerAmount = floor(size × price)  (collateral you receive)
```

All values are scaled to the collateral's `decimals`. **BUY ceils** the
collateral and **SELL floors** it — this asymmetric rounding is what keeps the
on-chain crossing check satisfied. It costs at most 1 wei.

```ts theme={null}
const DECIMALS = 18n; // BSC — confirm via /tokens

function toWei(decimal: string, decimals: bigint): bigint {
  const [w = "0", f = ""] = decimal.split(".");
  const frac = (f + "0".repeat(Number(decimals))).slice(0, Number(decimals));
  return BigInt(w) * 10n ** decimals + BigInt(frac || "0");
}

function computeAmounts(side: "BUY" | "SELL", price: string, size: string, decimals: bigint) {
  const sizeWei = toWei(size, decimals);
  const priceWei = toWei(price, decimals);
  const denom = 10n ** decimals;
  const collateral =
    side === "BUY"
      ? (sizeWei * priceWei + denom - 1n) / denom // ceil
      : (sizeWei * priceWei) / denom;             // floor
  return side === "BUY"
    ? { makerAmount: collateral, takerAmount: sizeWei }
    : { makerAmount: sizeWei, takerAmount: collateral };
}
```

<Warning>
  Stay in BigInt the entire way. `Number`, `parseFloat`, and `* price` on a
  float silently corrupt 18-decimal amounts by a few wei, which the ingest
  validator rejects. Compute collateral as `size × price` at full scale, then
  ceil (BUY) or floor (SELL).
</Warning>

## Complete example (viem)

```ts theme={null}
import { createWalletClient, custom } from "viem";

const account = "0xYourWallet...";
const market = await (
  await fetch("https://api.foresight.now/v1/markets/" + conditionId + "?chain_id=56")
).json();
const { tokens } = await (
  await fetch(
    "https://api.foresight.now/v1/markets/" + conditionId + "/tokens?chain_id=56"
  )
).json();

const outcome = 1; // YES
const yes = tokens.find((t) => t.outcome === outcome);
const decimals = BigInt(yes.decimals); // 18 on BSC

const side = "BUY";
const price = quantizePrice("0.55");
const size = "100";
const { makerAmount, takerAmount } = computeAmounts(side, price, size, decimals);

const struct = {
  salt: randomUint256(),               // e.g. crypto.getRandomValues -> decimal string
  maker: account,
  signer: account,                     // maker must equal signer
  taker: "0x0000000000000000000000000000000000000000",
  tokenId: yes.token_id,
  makerAmount: makerAmount.toString(),
  takerAmount: takerAmount.toString(),
  expiration: "0",                     // no expiry
  nonce: "0",
  feeRateBps: "100",
  side: side === "BUY" ? 0 : 1,
  signatureType: 0,                    // EOA
};

const walletClient = createWalletClient({ account, transport: custom(window.ethereum) });

const signature = await walletClient.signTypedData({
  account,
  domain: {
    name: "ForesightExchange",
    version: "1",
    chainId: 56,
    verifyingContract: market.ctf_exchange_address,
  },
  types: {
    Order: [
      { name: "salt", type: "uint256" },
      { name: "maker", type: "address" },
      { name: "signer", type: "address" },
      { name: "taker", type: "address" },
      { name: "tokenId", type: "uint256" },
      { name: "makerAmount", type: "uint256" },
      { name: "takerAmount", type: "uint256" },
      { name: "expiration", type: "uint256" },
      { name: "nonce", type: "uint256" },
      { name: "feeRateBps", type: "uint256" },
      { name: "side", type: "uint8" },
      { name: "signatureType", type: "uint8" },
    ],
  },
  primaryType: "Order",
  message: struct,
});
```

## Submit the order

The request body wraps the signed struct in snake\_case, plus the human-readable
`order_type`, `outcome`, `price`, and `size`:

```bash theme={null}
curl -X POST https://api.foresight.now/v1/orders \
  -H "fs-api-key: $FS_API_KEY" \
  -H "fs-api-secret: $FS_API_SECRET" \
  -H "fs-idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "orders": [{
      "condition_id": "0x...",
      "chain_id": 56,
      "side": "BUY",
      "order_type": "LIMIT",
      "outcome": 1,
      "price": "0.55",
      "size": "100",
      "salt": "...",
      "signer": "0xYourWallet...",
      "taker": "0x0000000000000000000000000000000000000000",
      "token_id": "5678...",
      "maker_amount": "...",
      "taker_amount": "...",
      "expiration": "0",
      "nonce": "0",
      "fee_rate_bps": 100,
      "signature_type": 0,
      "signature": "0x..."
    }]
  }'
```

You can batch up to **15** orders per request. The response always returns
`status: OPEN` for accepted orders — matching is asynchronous:

```json theme={null}
{
  "results": [
    {
      "order_hash": "0x...",
      "condition_id": "0x...",
      "chain_id": 56,
      "status": "OPEN",
      "side": "BUY",
      "outcome": 1,
      "price": "0.55",
      "size": "100",
      "remaining_size": "100",
      "fills": [],
      "book_added_at": null,
      "created_at": "2026-06-03T..."
    }
  ]
}
```

<Info>
  Each item in `results[]` is **either** a success **or** an inline
  `{ "error": { "code", "message" } }`. A mixed array is normal — always inspect
  every item. Fills and status transitions arrive on the
  [`user` WebSocket channel](/foresight-apis/clob/websocket), not here.
</Info>

## Idempotency

Set an `fs-idempotency-key` header on `POST /v1/orders` to make retries safe.
Replaying the **same key + same body** returns the cached result with
`_idempotent_replayed: true`. The **same key + a different body** returns
`IDEMPOTENCY_CONFLICT` (409). Idempotency is honored on order placement only.

## Cancelling

* `POST /v1/orders/cancel` — cancel by hash (up to 100), by filter
  (`condition_id` + `chain_id` + optional `outcome`/`side`), or all (empty body).
* `POST /v1/orders/{order_hash}/cancel-onchain` — returns unsigned calldata for a
  hard on-chain cancel against the CTF Exchange. You submit and pay for that
  transaction yourself; the backend does not send it.
