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

# Trading & Orders

> Create and manage orders with the TypeScript SDK

## Prerequisites

Before placing orders, initialize the required clients:

```typescript theme={null}
import { ethers } from 'ethers';
import {
  HttpClient,
  MarketFetcher,
  OrderClient,
} from '@limitless-exchange/sdk';

const httpClient = new HttpClient({
  baseURL: 'https://api.limitless.exchange',
  hmacCredentials: {
    tokenId: process.env.LMTS_TOKEN_ID,
    secret: process.env.LMTS_TOKEN_SECRET,
  },
});

const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!);
const marketFetcher = new MarketFetcher(httpClient);

const orderClient = new OrderClient({
  httpClient,
  wallet,
  marketFetcher, // Optional: enables venue caching for faster order creation
});
```

## OrderClient constructor

| Option          | Type            | Required | Description                                                                       |
| --------------- | --------------- | -------- | --------------------------------------------------------------------------------- |
| `httpClient`    | `HttpClient`    | Yes      | Authenticated HTTP client                                                         |
| `wallet`        | `ethers.Wallet` | Yes      | Wallet for EIP-712 signing                                                        |
| `marketFetcher` | `MarketFetcher` | No       | Enables automatic venue caching. If omitted, venue data is fetched on each order. |

<Note>
  The `OrderClient` automatically fetches your user profile data (including `ownerId`) from the API on the first order. You do not need to supply it manually.
</Note>

<Warning>
  **Wallet-mode preflight.** Accepting the one-time "choose your trading wallet" prompt in the app enables 1-click (smart wallet) trading on your Limitless profile. Once that mode is set, self-signed orders are rejected with `Signer does not match - you should use embedded address for smart wallet`. Switch the profile to EOA trading mode first: see [Trading wallet mode](/developers/eip712-signing#trading-wallet-mode-whose-address-signs).
</Warning>

## Token approvals

Before trading, your wallet must approve the relevant venue contracts to spend USDC and conditional tokens. This is a one-time setup per venue.

### Approval requirements

| Market type | USDC approval               | Conditional token approval  |
| ----------- | --------------------------- | --------------------------- |
| CLOB        | Venue `exchange` contract   | Venue `exchange` contract   |
| NegRisk     | NegRisk `exchange` contract | NegRisk `exchange` contract |

### Manual approval with ethers

```typescript theme={null}
import { ethers } from 'ethers';

const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
const ERC20_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
];
const ERC1155_ABI = [
  'function setApprovalForAll(address operator, bool approved)',
];

const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);

async function approveVenue(venueExchange: string) {
  const usdc = new ethers.Contract(USDC_ADDRESS, ERC20_ABI, signer);
  const tx1 = await usdc.approve(venueExchange, ethers.MaxUint256);
  await tx1.wait();
  console.log('USDC approved for', venueExchange);
}
```

<Tip>
  Run approvals once per venue. After approval, all subsequent orders on markets using that venue work without additional on-chain transactions.
</Tip>

## GTC orders (Good-til-cancelled)

GTC orders remain on the orderbook until filled, cancelled, or the market resolves. Specify `price` (probability between 0 and 1) and `size` (number of shares).

### Buy YES shares

```typescript theme={null}
import { OrderType, Side } from '@limitless-exchange/sdk';

const market = await marketFetcher.getMarket('btc-100k-weekly');

const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.yes, // YES token
  side: Side.BUY,
  price: 0.65,
  size: 100,
  orderType: OrderType.GTC,
});

console.log('Order created:', result.order.id);
```

### Post-only GTC order

Use `postOnly: true` to ensure your order is never filled immediately as a taker. If the order would cross the spread (i.e., match against existing orders), it is **rejected** instead. This guarantees you always receive maker fees.

```typescript theme={null}
const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.yes, // YES token
  side: Side.BUY,
  price: 0.65,
  size: 100,
  orderType: OrderType.GTC,
  postOnly: true,
});
```

### Sell NO shares

```typescript theme={null}
const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.no, // NO token
  side: Side.SELL,
  price: 0.40,
  size: 50,
  orderType: OrderType.GTC,
});
```

### GTC parameter reference

| Parameter    | Type        | Description                                                                              |
| ------------ | ----------- | ---------------------------------------------------------------------------------------- |
| `marketSlug` | `string`    | Market slug identifier                                                                   |
| `tokenId`    | `string`    | YES or NO token ID from `market.tokens` (yes / no)                                       |
| `side`       | `Side`      | `Side.BUY` (0) or `Side.SELL` (1)                                                        |
| `price`      | `number`    | Price between 0 and 1 (exclusive), tick-aligned                                          |
| `size`       | `number`    | Number of shares (positive, step-aligned)                                                |
| `orderType`  | `OrderType` | `OrderType.GTC`                                                                          |
| `postOnly`   | `boolean`   | Optional. When `true`, rejects the order if it would immediately match. Default `false`. |

## Self-trade prevention

Pass `stpPolicy` to control what happens when your incoming order would match your own resting order on the same token. It is a top-level request field — not part of the EIP-712 signed order — and applies to any order type. Omit it to keep the server default, `cancel_maker`.

```typescript theme={null}
const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.positionIds[0], // YES token
  side: Side.BUY,
  price: 0.65,
  size: 100,
  orderType: OrderType.GTC,
  stpPolicy: 'cancel_maker', // 'cancel_maker' | 'cancel_taker' | 'cancel_both'
});
```

| Value          | Result                                                                                 |
| -------------- | -------------------------------------------------------------------------------------- |
| `cancel_maker` | Default. Cancels your conflicting resting order and continues with the incoming order. |
| `cancel_taker` | Rejects the incoming order before it self-trades.                                      |
| `cancel_both`  | Cancels your conflicting resting order and rejects the incoming order.                 |

The create-order response carries an `execution` object with the outcome:

```typescript theme={null}
if (result.execution.settlementStatus === 'CANCELED' &&
    result.execution.reason === 'STP_TAKER_REJECTED') {
  console.log('Order rejected by self-trade prevention');
} else if (result.execution.stpMakerCancels?.length) {
  console.log('Cancelled own resting orders:', result.execution.stpMakerCancels);
}
```

| `execution` field  | Type        | Description                                                         |
| ------------------ | ----------- | ------------------------------------------------------------------- |
| `settlementStatus` | `string`    | `CANCELED` when a `cancel_taker` / `cancel_both` order is rejected. |
| `reason`           | `string?`   | `STP_TAKER_REJECTED` when the incoming order was rejected.          |
| `stpMakerCancels`  | `string[]?` | Resting order ids cancelled by `cancel_maker` / `cancel_both`.      |

<Note>
  Self-trade prevention blocks same-profile matches on the **same token** only. Orders on a different token of the same profile are unaffected. The wire field is always `stpPolicy`, regardless of the SDK.
</Note>

## FAK orders (Fill-and-kill)

FAK orders use the same `price` and `size` inputs as GTC, but they only consume immediately available liquidity and cancel any unmatched remainder.

`postOnly` is not supported for FAK orders.

### Buy with FAK

```typescript theme={null}
const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.yes, // YES token
  side: Side.BUY,
  price: 0.45,
  size: 100,
  orderType: OrderType.FAK,
});

if (result.makerMatches.length > 0) {
  console.log(`FAK order matched immediately with ${result.makerMatches.length} fill(s)`);
} else {
  console.log('FAK remainder was cancelled.');
}
```

### Sell with FAK

```typescript theme={null}
const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.no, // NO token
  side: Side.SELL,
  price: 0.40,
  size: 50,
  orderType: OrderType.FAK,
});
```

### FAK parameter reference

| Parameter    | Type        | Description                                        |
| ------------ | ----------- | -------------------------------------------------- |
| `marketSlug` | `string`    | Market slug identifier                             |
| `tokenId`    | `string`    | YES or NO token ID from `market.tokens` (yes / no) |
| `side`       | `Side`      | `Side.BUY` (0) or `Side.SELL` (1)                  |
| `price`      | `number`    | Price between 0 and 1 (exclusive), tick-aligned    |
| `size`       | `number`    | Number of shares (positive, step-aligned)          |
| `orderType`  | `OrderType` | `OrderType.FAK`                                    |

## FOK orders (Fill-or-kill)

FOK orders execute immediately against the existing orderbook or are rejected entirely. Instead of `price` and `size`, specify `makerAmount` which represents the total value to trade.

### Buy with FOK

For a BUY FOK order, `makerAmount` is the amount of USDC you are willing to spend:

```typescript theme={null}
import { OrderType, Side } from '@limitless-exchange/sdk';

const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.yes, // YES token
  side: Side.BUY,
  makerAmount: 50, // Spend up to 50 USDC
  orderType: OrderType.FOK,
});
```

### Sell with FOK

For a SELL FOK order, `makerAmount` is the number of shares you want to sell:

```typescript theme={null}
const result = await orderClient.createOrder({
  marketSlug: market.slug,
  tokenId: market.tokens.yes, // YES token
  side: Side.SELL,
  makerAmount: 100, // Sell 100 shares
  orderType: OrderType.FOK,
});
```

### FOK parameter reference

| Parameter     | Type        | Description                                               |
| ------------- | ----------- | --------------------------------------------------------- |
| `marketSlug`  | `string`    | Market slug identifier                                    |
| `tokenId`     | `string`    | YES or NO token ID from `market.tokens` (yes / no)        |
| `side`        | `Side`      | `Side.BUY` (0) or `Side.SELL` (1)                         |
| `makerAmount` | `number`    | BUY: USDC to spend. SELL: shares to sell. Max 6 decimals. |
| `orderType`   | `OrderType` | `OrderType.FOK`                                           |

## NegRisk orders

When trading NegRisk markets, always use the **submarket slug**. The group slug is not tradeable. Fetch the group to discover submarkets, then fetch the specific submarket to get its token IDs.

```typescript theme={null}
import { OrderType, Side } from '@limitless-exchange/sdk';

// 1. Fetch the NegRisk group
const group = await marketFetcher.getMarket('us-election-2024');

// 2. Pick a submarket from the group's markets array
const submarket = group.markets[0];

// 3. Fetch submarket details to get token IDs
const submarketDetail = await marketFetcher.getMarket(submarket.slug);

// 4. Place an order using the submarket slug and token IDs
const result = await orderClient.createOrder({
  marketSlug: submarketDetail.slug,
  tokenId: submarketDetail.tokens.yes, // YES token
  side: Side.BUY,
  price: 0.55,
  size: 25,
  orderType: OrderType.GTC,
});
```

<Warning>
  Passing the group slug to `createOrder()` will fail. The group slug does not resolve to a tradeable market. Always use the submarket slug.
</Warning>

## AMM trading

CLOB orders trade against the orderbook. AMM (FPMM) markets trade against a pool, and the SDK exposes them through `client.amm`. The service calls `POST /amm/allowances/check`, `POST /amm/allowances/approve`, `POST /amm/buy`, and `POST /amm/sell` on behalf of a partner server wallet.

Use it when the market is an AMM market and you want the server to hold custody, sign the trade, and pay gas. See [AMM Trading (Server Wallets)](/developers/amm-trading) for the underlying endpoints and market model.

### Requirements

* Authenticate with an HMAC API token that holds **both** the `trading` and `delegated_signing` scopes, or pass a per-call Privy `identityToken`. Legacy `x-api-key` credentials are rejected.
* The trade runs against a server-wallet sub-account. Set `onBehalfOf` to the sub-account profile ID, or omit it to trade from the authenticated profile.
* Amounts are **positive integer strings** in the collateral token's base units (for USDC: `"1000000"` = 1 USDC). Never pass floats.
* `slippageBps` is optional and ranges from `0` to `1000`. The server default is `100` (1%).
* `outcomeIndex` is `0` for YES and `1` for NO.

### One-time approval per wallet and market

`BUY` and `SELL` approvals are independent and set up once per wallet and market. `buy` and `sell` do **not** preflight allowances themselves. Confirm the allowance first.

`ensureAllowance` runs `checkAllowance`, submits `approveAllowance` at most once when missing, then polls the check until `confirmed` is true. Tune the poll with `intervalMs` (default `2000`) and `maxAttempts` (default `30`).

```typescript theme={null}
import { Client } from '@limitless-exchange/sdk';

const client = new Client({
  hmacCredentials: {
    tokenId: process.env.LMTS_TOKEN_ID!,
    secret: process.env.LMTS_TOKEN_SECRET!,
  },
});

const marketSlug = 'btc-100k-weekly';
const childProfileId = 12345;

await client.amm.ensureAllowance({ market: marketSlug, side: 'BUY', onBehalfOf: childProfileId });
await client.amm.ensureAllowance({ market: marketSlug, side: 'SELL', onBehalfOf: childProfileId });
```

<Note>
  A `submitted` response from `approveAllowance` (HTTP 202) is not confirmation. Either use `ensureAllowance`, or poll `checkAllowance` until `confirmed` is true.
</Note>

### Buy shares

`buy` spends an exact collateral amount on the chosen outcome. Pass a unique `idempotencyKey` per trade. On a timeout retry, reuse the exact same params so the server replays the original submission rather than opening a second trade.

```typescript theme={null}
const buy = await client.amm.buy({
  market: marketSlug,
  outcomeIndex: 0,             // 0 = YES, 1 = NO
  collateralAmount: '1000000', // 1 USDC in base units
  slippageBps: 100,            // optional, 0..1000
  idempotencyKey: 'buy-unique-key-001',
  onBehalfOf: childProfileId,  // omit for a direct profile
});

console.log(buy.status, buy.expectedShares, buy.minShares);
```

### Sell shares

`sell` requests an exact collateral return by selling outcome shares.

```typescript theme={null}
const sell = await client.amm.sell({
  market: marketSlug,
  outcomeIndex: 0,
  collateralReturnAmount: '992015',
  idempotencyKey: 'sell-unique-key-001',
  onBehalfOf: childProfileId,
});

console.log(sell.status, sell.expectedShares, sell.maxShares);
```

<Note>
  Reusing an `idempotencyKey` with different params raises `ConflictError` (HTTP 409). The four AMM routes share a rate limit of 10 requests / 10 seconds per actor. Pass `{ withRawResponse: true }` to any AMM method to receive an `SdkResponse` with HTTP status and headers.
</Note>

## Cancelling orders

Cancel an open order by its ID:

```typescript theme={null}
const result = await orderClient.cancel('order_abc123');
console.log(result.message);
```

Cancel all orders in a market:

```typescript theme={null}
const result = await orderClient.cancelAll('btc-100k-weekly');
console.log(result.message);
```

## Cancel and replace

`cancelReplace` cancels one open order and submits a replacement in the same request. Use it to reprice or resize a resting order in one round-trip rather than issuing separate cancel + create calls. `cancelReplaceBatch` runs several of these operations in a single call.

The two actions are **not atomic**: the cancel and the replacement have independent outcomes, and a successful cancel does not guarantee a successful replacement. Choose the failure mode with `CancelReplaceMode`:

| Mode              | Cancellation fails      | Replacement result                               |
| ----------------- | ----------------------- | ------------------------------------------------ |
| `STOP_ON_FAILURE` | Stops the operation     | `NOT_ATTEMPTED`                                  |
| `ALLOW_FAILURE`   | Continues the operation | `SUCCESS`, `FAILURE`, or `UNKNOWN` independently |

Identify the order to cancel by exactly one of `orderId` or `clientOrderId`. The replacement is a normal signed order and accepts the same fields as `createOrder` (`orderType`, `marketSlug`, `tokenId`, `side`, `price`, `size` or `makerAmount`, `postOnly`, `stpPolicy`, etc.).

### Single cancel-replace

```typescript theme={null}
import { CancelReplaceMode, OrderType, Side } from '@limitless-exchange/sdk';

const result = await orderClient.cancelReplace({
  cancel: { orderId: 'order_abc123' },
  mode: CancelReplaceMode.STOP_ON_FAILURE,
  replacement: {
    marketSlug: market.slug,
    orderType: OrderType.GTC,
    tokenId: market.tokens.yes,
    side: Side.BUY,
    price: 0.62,
    size: 100,
  },
});

if (result.cancel.status === 'SUCCESS') {
  console.log('Cancelled:', result.cancel.orderId);
}
if (result.replacement.status === 'SUCCESS') {
  console.log('Replacement placed:', result.replacement.data.id);
}
```

### Batch cancel-replace

Each entry runs independently and its result is returned with the caller's `index`:

```typescript theme={null}
const batch = await orderClient.cancelReplaceBatch({
  operations: [
    {
      cancel: { orderId: 'order_abc123' },
      mode: CancelReplaceMode.ALLOW_FAILURE,
      replacement: { marketSlug: market.slug, orderType: OrderType.GTC, tokenId: market.tokens.yes, side: Side.BUY, price: 0.62, size: 100 },
    },
    {
      cancel: { clientOrderId: 'my-tag-42' },
      mode: CancelReplaceMode.STOP_ON_FAILURE,
      replacement: { marketSlug: market.slug, orderType: OrderType.GTC, tokenId: market.tokens.no, side: Side.SELL, price: 0.41, size: 50 },
    },
  ],
});

for (const item of batch.results) {
  console.log(item.index, item.cancel.status, item.replacement.status);
}
```

### Delegated cancel-replace

Partners with the `delegated_signing` scope call `client.delegatedOrders.cancelReplace` and `client.delegatedOrders.cancelReplaceBatch`. The server signs the replacement using the sub-account's managed wallet, so no private key is required. Set `onBehalfOf` to the sub-account profile ID on every operation:

```typescript theme={null}
await client.delegatedOrders.cancelReplace({
  cancel: { orderId: 'order_abc123' },
  mode: CancelReplaceMode.STOP_ON_FAILURE,
  onBehalfOf: partnerAccount.profileId,
  replacement: {
    marketSlug: market.slug,
    orderType: OrderType.GTC,
    tokenId: market.tokens.yes,
    side: Side.BUY,
    price: 0.62,
    size: 100,
  },
});
```

<Note>
  See [`POST /orders/cancel-replace`](/api-reference/trading/cancel-replace) and [`POST /orders/cancel-replace/batch`](/api-reference/trading/cancel-replace-batch) for the full request and response shapes, per-status fields, and failure semantics.
</Note>

## Order states

| State              | Description                                               |
| ------------------ | --------------------------------------------------------- |
| `OPEN`             | Order is live on the orderbook                            |
| `PARTIALLY_FILLED` | Some shares have been matched                             |
| `FILLED`           | Order fully matched                                       |
| `CANCELLED`        | Order cancelled by user or system                         |
| `REJECTED`         | Order rejected (insufficient funds, invalid params, etc.) |

## Error handling

The SDK throws `APIError` for HTTP-level failures. Inspect `status` and `message` for details.

```typescript theme={null}
import { APIError, OrderType, Side } from '@limitless-exchange/sdk';

try {
  await orderClient.createOrder({
    marketSlug: 'btc-100k-weekly',
    tokenId: market.tokens.yes,
    side: Side.BUY,
    price: 0.65,
    size: 100,
    orderType: OrderType.GTC,
  });
} catch (error) {
  if (error instanceof APIError) {
    switch (error.status) {
      case 400:
        console.error('Bad request:', error.message);
        break;
      case 401:
        console.error('Unauthorized: check your API key');
        break;
      case 429:
        console.error('Rate limited: slow down requests');
        break;
      default:
        console.error(`API error ${error.status}:`, error.message);
    }
  } else {
    throw error;
  }
}
```

See the [Error Handling & Retry](/developers/sdk/typescript/error-handling) guide for retry strategies.

## Validation rules

<Accordion title="Price validation">
  Prices must be between 0 and 1 (exclusive). A price of `0.65` means you value the outcome at 65%. The API rejects prices outside this range.

  ```typescript theme={null}
  // Valid
  price: 0.01
  price: 0.99

  // Invalid (will throw)
  price: 0
  price: 1
  price: 1.5
  price: -0.1
  ```
</Accordion>

<Accordion title="Size validation">
  Size must be a positive number. Fractional sizes are allowed if they align to the shares step (0.001). Max 3 decimal places.

  ```typescript theme={null}
  // Valid
  size: 1
  size: 100
  size: 1.5
  size: 0.001

  // Invalid (will throw)
  size: 0
  size: -1
  size: 0.0001  // too many decimals
  ```
</Accordion>
