> ## 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.

# Node.js / TypeScript Quick Start

> End-to-end TypeScript implementation with viem for trading on Limitless Exchange

## Overview

This guide walks through a complete Node.js/TypeScript integration with the Limitless Exchange API: authentication, market data, EIP-712 order signing with viem, order submission, and WebSocket subscriptions for real-time data.

<Warning>
  **Never expose your private key.** Use environment variables and never commit secrets to version control. For production, consider a dedicated key management solution.
</Warning>

## Prerequisites

Install the required dependencies:

<Tabs>
  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add socket.io-client cross-fetch ethers viem
    ```
  </Tab>

  <Tab title="npm">
    ```bash theme={null}
    npm install socket.io-client cross-fetch ethers viem
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add socket.io-client cross-fetch ethers viem
    ```
  </Tab>
</Tabs>

| Package            | Purpose                                           |
| ------------------ | ------------------------------------------------- |
| `socket.io-client` | WebSocket connection to real-time market data     |
| `cross-fetch`      | HTTP requests (or use native `fetch` in Node 18+) |
| `ethers`           | Optional; viem handles EIP-712 signing            |
| `viem`             | EIP-712 signing, wallet client, `parseUnits`      |

<Tip>
  If using Node 18+, you can omit `cross-fetch` and use the built-in `fetch`.
</Tip>

## Environment Variables

Create a `.env` file (and add it to `.gitignore`):

```bash theme={null}
LMTS_TOKEN_ID=your_token_id
LMTS_TOKEN_SECRET=your_base64_secret
PRIVATE_KEY=0x...
API_URL=https://api.limitless.exchange
OWNER_ID=12345
```

<Warning>
  `OWNER_ID` is your Limitless profile ID (numeric). Obtain it from your account settings or from an authenticated API response. It is required for order submission.
</Warning>

## 1. Authentication

Authenticated REST requests (e.g. submitting orders) are signed with HMAC-SHA256 using your scoped API token. Each request carries three headers — `lmts-api-key`, `lmts-timestamp`, and `lmts-signature` — computed over a canonical message of `timestamp`, HTTP method, request path (with query string), and body. Public market data (browsing markets, orderbooks) needs no authentication. See [Authentication](/developers/authentication) for the full reference.

<CodeGroup>
  ```typescript api.ts theme={null}
  import { createHmac } from 'crypto';

  const API_URL = process.env.API_URL ?? 'https://api.limitless.exchange';
  const TOKEN_ID = process.env.LMTS_TOKEN_ID;
  const TOKEN_SECRET = process.env.LMTS_TOKEN_SECRET; // base64-encoded

  // Build HMAC auth headers for a single request (method + path + body).
  function signRequest(
  tokenId: string,
  secret: string,
  method: string,
  path: string,
  body: string = '',
  ): Record<string, string> {
  const timestamp = new Date().toISOString();
  const message = `${timestamp}\n${method}\n${path}\n${body}`;
  const signature = createHmac('sha256', Buffer.from(secret, 'base64')).update(message).digest('base64');
  return { 'lmts-api-key': tokenId, 'lmts-timestamp': timestamp, 'lmts-signature': signature };
  }

  // Public endpoints (market data, orderbooks) require no authentication.
  export async function apiGet<T>(path: string): Promise<T> {
  const res = await fetch(`${API_URL}${path}`);
  if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
  return res.json();
  }

  // Authenticated POST — signs the exact body bytes that are sent.
  export async function apiPost<T>(path: string, body: unknown): Promise<T> {
  if (!TOKEN_ID || !TOKEN_SECRET) {
    throw new Error('Missing LMTS_TOKEN_ID / LMTS_TOKEN_SECRET. Derive a token at limitless.exchange → API Tokens');
  }
  const serialized = JSON.stringify(body);
  const auth = signRequest(TOKEN_ID, TOKEN_SECRET, 'POST', path, serialized);
  const res = await fetch(`${API_URL}${path}`, {
    method: 'POST',
    headers: { ...auth, 'Content-Type': 'application/json' },
    body: serialized,
  });
  if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
  return res.json();
  }
  ```

  ```bash curl theme={null}
  # GET request (public — no auth)
  curl https://api.limitless.exchange/markets/btc-100k-weekly

  # POST request: HMAC-signed. Compute lmts-signature over
  # "{timestamp}\nPOST\n/orders\n{body}" with HMAC-SHA256 (base64-decoded secret),
  # then base64-encode it. The timestamp must be within 30s of server time.
  curl -X POST \
  -H "lmts-api-key: $LMTS_TOKEN_ID" \
  -H "lmts-timestamp: $LMTS_TIMESTAMP" \
  -H "lmts-signature: $LMTS_SIGNATURE" \
  -H "Content-Type: application/json" \
  -d '{"order":{...},"orderType":"GTC","marketSlug":"btc-100k-weekly","ownerId":12345}' \
  https://api.limitless.exchange/orders
  ```
</CodeGroup>

<Note>
  Generating `lmts-signature` by hand on the command line is awkward — for shell usage, run the `signRequest` helper above (or your SDK) to produce the three headers. See [Authentication](/developers/authentication) for ready-to-use signing snippets.
</Note>

## 2. Fetching Market Data and Caching Venue Info

Fetch market details via `GET /markets/:slug`. The response includes `venue` (exchange, adapter) and `tokens` (the YES and NO token IDs) for CLOB markets.

<Steps>
  <Step title="Fetch market by slug">
    Call `GET /markets/{slug}` to retrieve market data including venue addresses.
  </Step>

  <Step title="Extract venue and token IDs">
    Use `venue.exchange` as the EIP-712 `verifyingContract`. Use `tokens.yes` for YES and `tokens.no` for NO.
  </Step>

  <Step title="Cache the venue">
    Venue data is static per market. Fetch once and reuse for all orders on that market.
  </Step>
</Steps>

```typescript theme={null}
interface Venue {
  exchange: string;
  adapter: string | null;
}

interface ClobMarket {
  slug: string;
  venue: Venue;
  tokens: { yes: string; no: string };  // YES / NO token IDs
}

export async function getMarket(slug: string): Promise<ClobMarket> {
  const data = await apiGet<ClobMarket>(`/markets/${slug}`);
  if (!data.venue?.exchange || !data.tokens?.yes || !data.tokens?.no) {
    throw new Error(`Market ${slug} is not a CLOB market or missing venue data`);
  }
  return data;
}
```

## 3. Creating Signed Orders with viem (EIP-712)

Build the order payload and sign it with viem's `signTypedData`. The domain uses `venue.exchange` as `verifyingContract`.

### EIP-712 Domain and Types

```typescript theme={null}
import { createWalletClient, http, privateKeyToAccount } from 'viem';
import { base } from 'viem/chains';
import { parseUnits } from 'viem';

const chainId = 8453;  // Base

const orderType = {
  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' },
  ],
} as const;
```

### Signing Logic

```typescript theme={null}
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';

export async function createSignedOrder(params: {
  market: ClobMarket;
  side: 0 | 1;           // 0 = BUY, 1 = SELL
  outcome: 'yes' | 'no'; // tokens.yes = YES, tokens.no = NO
  priceCents: number;    // e.g. 65 = $0.65
  shares: number;       // number of shares
  orderType: 'GTC' | 'FOK';
  nonce: number;
}): Promise<{ order: Record<string, unknown>; signature: string }> {
  const { market, side, outcome, priceCents, shares, nonce } = params;
  const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
  if (!privateKey) throw new Error('PRIVATE_KEY required');

  const account = privateKeyToAccount(privateKey);
  const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http(),
  });

  const tokenId = outcome === 'yes' ? market.tokens.yes : market.tokens.no;
  const price = priceCents / 100;
  const sharesScaled = parseUnits(shares.toString(), 6);
  const makerAmount = side === 0
    ? parseUnits((price * shares).toFixed(6), 6)  // BUY: offer USDC
    : sharesScaled;                                 // SELL: offer shares
  const takerAmount = side === 0
    ? sharesScaled                                  // BUY: want shares
    : parseUnits((price * shares).toFixed(6), 6);   // SELL: want USDC

  const salt = BigInt(Date.now());
  const expiration = 0n;

  const order = {
    salt,
    maker: account.address,
    signer: account.address,
    taker: ZERO_ADDRESS,
    tokenId: BigInt(tokenId),
    makerAmount,
    takerAmount,
    expiration,
    nonce: BigInt(nonce),
    feeRateBps: 0n,
    side: params.side as 0 | 1,
    signatureType: 0,  // EOA
  };

  const domain = {
    name: 'Limitless CTF Exchange',
    version: '1',
    chainId,
    verifyingContract: market.venue.exchange as `0x${string}`,
  };

  const signature = await walletClient.signTypedData({
    domain,
    types: orderType,
    primaryType: 'Order',
    message: order,
  });

  return {
    // Send large integer fields as decimal strings to preserve precision
    // (salt can exceed JS's safe-integer range; Number() would corrupt it).
    order: {
      salt: order.salt.toString(),
      maker: account.address,
      signer: account.address,
      taker: ZERO_ADDRESS,
      tokenId: tokenId,
      makerAmount: order.makerAmount.toString(),
      takerAmount: order.takerAmount.toString(),
      expiration: '0',
      nonce: order.nonce.toString(),
      feeRateBps: 0,
      side: params.side,
      signatureType: 0,
      signature,
    },
    signature,
  };
}
```

<Note>
  USDC uses 6 decimals. Use `parseUnits(value, 6)` for amounts. Prices are in dollars; multiply `price * shares` before scaling.
</Note>

## 4. Submitting Orders

Submit the signed order to `POST /orders` with `order`, `orderType`, `marketSlug`, and `ownerId`.

```typescript theme={null}
interface CreateOrderPayload {
  order: Record<string, unknown>;
  orderType: 'GTC' | 'FOK';
  marketSlug: string;
  ownerId: number;
}

export async function submitOrder(payload: CreateOrderPayload) {
  return apiPost<{ order: unknown; makerMatches?: unknown[] }>('/orders', payload);
}

// Example usage
const market = await getMarket('btc-100k-weekly');
const { order } = await createSignedOrder({
  market,
  side: 0,           // BUY
  outcome: 'yes',
  priceCents: 65,
  shares: 10,
  orderType: 'GTC',
  nonce: 0,
});

const result = await submitOrder({
  order,
  orderType: 'GTC',
  marketSlug: market.slug,
  ownerId: Number(process.env.OWNER_ID),
});
```

## 5. WebSocket Subscription for Real-Time Data

Connect to `wss://ws.limitless.exchange` with namespace `/markets` using `socket.io-client`. Emit `subscribe_market_prices` with `marketAddresses` and/or `marketSlugs`.

<Warning>
  Subscriptions **replace** previous ones. To subscribe to both AMM (by address) and CLOB (by slug), send both `marketAddresses` and `marketSlugs` in a single `subscribe_market_prices` call.
</Warning>

```typescript theme={null}
import { io } from 'socket.io-client';

const WS_URL = 'wss://ws.limitless.exchange';
const NAMESPACE = '/markets';

// Market price/orderbook streams are public — no authentication required.
const socket = io(`${WS_URL}${NAMESPACE}`, {
  transports: ['websocket'],
});

socket.on('connect', () => {
  socket.emit('subscribe_market_prices', {
    marketAddresses: ['0x1234...'],   // AMM markets
    marketSlugs: ['btc-100k-weekly'], // CLOB markets
  });
});

socket.on('newPriceData', (data: { marketAddress: string; updatedPrices: { yes: string; no: string } }) => {
  console.log('AMM price update:', data.marketAddress, data.updatedPrices);
});

socket.on('orderbookUpdate', (data: { marketSlug: string; orderbook: unknown }) => {
  console.log('CLOB orderbook update:', data.marketSlug);
});

socket.on('disconnect', (reason) => {
  console.log('Disconnected:', reason);
});
```

| Event             | Description             |
| ----------------- | ----------------------- |
| `newPriceData`    | AMM market price update |
| `orderbookUpdate` | CLOB orderbook update   |

## Complete End-to-End Example

<CodeGroup>
  ```typescript index.ts theme={null}
  import { getMarket } from './market';
  import { createSignedOrder } from './orders';
  import { submitOrder } from './submit';
  import { io } from 'socket.io-client';

  const WS_URL = 'wss://ws.limitless.exchange';

  async function main() {
  const marketSlug = process.argv[2] ?? 'btc-100k-weekly';
  const market = await getMarket(marketSlug);
  console.log('Market:', market.slug, 'Venue:', market.venue.exchange);

  const { order } = await createSignedOrder({
    market,
    side: 0,
    outcome: 'yes',
    priceCents: 65,
    shares: 5,
    orderType: 'GTC',
    nonce: 0,
  });

  const result = await submitOrder({
    order,
    orderType: 'GTC',
    marketSlug: market.slug,
    ownerId: Number(process.env.OWNER_ID),
  });
  console.log('Order submitted:', result);

  // Public market stream — no authentication required.
  const socket = io(`${WS_URL}/markets`, {
    transports: ['websocket'],
  });

  socket.on('connect', () => {
    socket.emit('subscribe_market_prices', { marketSlugs: [marketSlug] });
  });

  socket.on('orderbookUpdate', (data) => {
    console.log('Orderbook:', data);
  });
  }

  main().catch(console.error);
  ```

  ```typescript minimal.ts theme={null}
  // Minimal: fetch market, sign order, submit
  const market = await getMarket('btc-100k-weekly');
  const { order } = await createSignedOrder({
  market, side: 0, outcome: 'yes', priceCents: 65, shares: 5, orderType: 'GTC', nonce: 0,
  });
  await submitOrder({ order, orderType: 'GTC', marketSlug: market.slug, ownerId: Number(process.env.OWNER_ID) });
  ```
</CodeGroup>

## Reference Summary

| Item                | Value                            |
| ------------------- | -------------------------------- |
| API base URL        | `https://api.limitless.exchange` |
| WebSocket URL       | `wss://ws.limitless.exchange`    |
| Namespace           | `/markets`                       |
| Chain               | Base (chainId: 8453)             |
| EIP-712 domain name | `Limitless CTF Exchange`         |
| EIP-712 version     | `1`                              |
| USDC decimals       | 6                                |
| Side                | 0 = BUY, 1 = SELL                |
| signatureType       | 0 = EOA                          |
| Order types         | GTC, FOK                         |

## Next Steps

<CardGroup cols={2}>
  <Card title="EIP-712 Signing" icon="pen" href="/developers/eip712-signing">
    Deep dive into order structure and signing.
  </Card>

  <Card title="Venue System" icon="building" href="/developers/venue-system">
    Token approvals and venue addresses.
  </Card>

  <Card title="WebSocket Events" icon="plug" href="/developers/websocket-events">
    Full event reference for real-time data.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Complete endpoint documentation.
  </Card>
</CardGroup>
