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

# Market Making Parlays

> How to listen for parlay requests, price them, and submit orders as a market maker.

## Overview

This guide covers the full flow for market making parlays on SX Bet: connecting to the WebSocket, receiving parlay requests, analyzing legs, calculating odds, and posting orders — all within the 3-second RFQ window.

## Step 1: Connect to the parlay channel

Subscribe to the [`parlay_markets:global` WebSocket channel](/api-reference/centrifugo-parlay-market-requests) to receive parlay requests in real-time. You'll need a token from the [`GET /user/realtime-token/api-key`](/api-reference/centrifugo-initialization) endpoint.

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Centrifuge } from "centrifuge";

  const API_KEY = process.env.SX_API_KEY;
  const RELAYER_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
  const WS_URL = "wss://realtime.sx.bet/connection/websocket"; // Mainnet — use wss://realtime.toronto.sx.bet/connection/websocket for testnet

  async function fetchToken() {
    const res = await fetch(`${RELAYER_URL}/user/realtime-token/api-key`, {
      headers: { "x-api-key": API_KEY },
    });
    if (!res.ok) throw new Error(`Token endpoint returned ${res.status}`);
    const { token } = await res.json();
    return token;
  }

  const client = new Centrifuge(WS_URL, {
    getToken: fetchToken,
  });

  const sub = client.newSubscription("parlay_markets:global");

  sub.on("publication", (ctx) => {
    const parlayRequest = ctx.data;
    handleParlayRequest(parlayRequest);
  });

  sub.subscribe();
  client.connect();
  ```

  ```python Python theme={null}
  import asyncio
  import aiohttp
  from centrifuge import Client, PublicationContext, SubscriptionEventHandler

  API_KEY = os.environ["SX_API_KEY"]
  RELAYER_URL = "https://api.sx.bet"  # Mainnet — use https://api.toronto.sx.bet for testnet
  WS_URL = "wss://realtime.sx.bet/connection/websocket"  # Mainnet — use wss://realtime.toronto.sx.bet/connection/websocket for testnet

  async def fetch_token():
      async with aiohttp.ClientSession() as session:
          async with session.get(
              f"{RELAYER_URL}/user/realtime-token/api-key",
              headers={"x-api-key": API_KEY},
          ) as resp:
              data = await resp.json()
              return data["token"]

  async def main():
      client = Client(WS_URL, get_token=fetch_token)

      async def on_publication(ctx: PublicationContext) -> None:
          handle_parlay_request(ctx.data)

      handler = SubscriptionEventHandler(on_publication=on_publication)
      sub = client.new_subscription("parlay_markets:global", handler)

      await client.connect()
      await sub.subscribe()
      await asyncio.Future()  # run forever

  asyncio.run(main())
  ```
</CodeGroup>

## Step 2: Parse the parlay request

Each incoming message contains the parlay's `marketHash`, the requested token and size, and the legs:

```json theme={null}
{
  "marketHash": "0x38cceead7bda65c18574a34994ebd8af154725d08aa735dcbf26247a7dcc67bd",
  "baseToken": "0x8f3Cf7ad23Cd3CaDb09735AFf958023239c6A063",
  "requestSize": "100000000",
  "legs": [
    {
      "marketHash": "0x0d64c52e8781acdada86920a2d1e5acd6f29dcfe285cf9cae367b671dff05f7d",
      "bettingOutcomeOne": true
    },
    {
      "marketHash": "0xe609a49d083cd41214a0db276c1ba323c4a947eefd2e4260386fec7b5d258188",
      "bettingOutcomeOne": false
    }
  ]
}
```

| Field         | Description                                                                                  |
| ------------- | -------------------------------------------------------------------------------------------- |
| `marketHash`  | The parlay market to post your order against                                                 |
| `baseToken`   | Token the bettor wants to bet in                                                             |
| `requestSize` | Requested size in base token units (see [`unit conversion`](/api-reference/unit-conversion)) |
| `legs`        | Array of individual markets and the outcomes the bettor selected                             |

<Info>The `requestSize` is what the bettor requested, but you can offer any size you want. You are not limited to matching their request.</Info>

## Step 3: Look up each leg

Query each leg's market via [`GET /markets/find`](/api-reference/get-markets-find) to understand what's being bet on, and check current odds via [`GET /orders/odds/best`](/api-reference/get-best-odds):

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function handleParlayRequest(parlayRequest) {
    const { marketHash: parlayMarketHash, baseToken, requestSize, legs } = parlayRequest;

    // Fetch market data for each leg
    const legData = await Promise.all(
      legs.map(async (leg) => {
        const response = await fetch(
          `${BASE_URL}/markets/find?${new URLSearchParams({ marketHash: leg.marketHash })}`
        ).then((r) => r.json());
        const market = response.data;

        // Fetch best odds for this leg's market
        const ordersResponse = await fetch(
          `${BASE_URL}/orders/odds/best?${new URLSearchParams({ marketHashes: leg.marketHash })}`
        ).then((r) => r.json());
        const bestOdds = ordersResponse.data.bestOdds;

        return {
          ...leg,
          market,
          bestOdds,
        };
      })
    );

    console.log("Parlay request received:");
    legData.forEach((leg, i) => {
      const m = leg.market;
      const side = leg.bettingOutcomeOne ? m.outcomeOneName : m.outcomeTwoName;
      console.log(`  Leg ${i + 1}: ${m.teamOneName} vs ${m.teamTwoName} — ${side}`);
    });

    // Calculate your price and post an order
    const odds = calculateParlayOdds(legData);
    if (odds) {
      await postParlayOrder(parlayMarketHash, baseToken, odds);
    }
  }
  ```

  ```python Python theme={null}
  def handle_parlay_request(parlay_request):
      parlay_market_hash = parlay_request["marketHash"]
      base_token = parlay_request["baseToken"]
      request_size = parlay_request["requestSize"]
      legs = parlay_request["legs"]

      # Fetch market data for each leg
      leg_data = []
      for leg in legs:
          market_resp = requests.get(
              f"{BASE_URL}/markets/find",
              params={"marketHash": leg["marketHash"]}
          )
          market = market_resp.json()["data"]

          odds_resp = requests.get(
              f"{BASE_URL}/orders/odds/best",
              params={"marketHashes": leg["marketHash"]}
          )
          best_odds = odds_resp.json()["data"]["bestOdds"]

          leg_data.append({**leg, "market": market, "bestOdds": best_odds})

      for i, leg in enumerate(leg_data):
          m = leg["market"]
          side = m["outcomeOneName"] if leg["bettingOutcomeOne"] else m["outcomeTwoName"]
          print(f"  Leg {i + 1}: {m['teamOneName']} vs {m['teamTwoName']} — {side}")

      # Calculate your price and post an order
      odds = calculate_parlay_odds(leg_data)
      if odds:
          post_parlay_order(parlay_market_hash, base_token, odds)
  ```
</CodeGroup>

## Step 4: Calculate your odds

The simplest approach is to multiply the implied probabilities of each leg. Your pricing strategy is up to you — this is a basic example:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const ODDS_PRECISION = 10n ** 20n;

  function calculateParlayOdds(legData) {
    // Simple approach: multiply implied probabilities of each leg
    // In practice, you may want to account for correlation between legs
    let combinedProbability = 1.0;

    for (const leg of legData) {
      // Use your own fair odds estimate per leg
      // This example uses a fixed estimate — replace with your model
      const legProbability = estimateLegProbability(leg);
      combinedProbability *= legProbability;
    }

    // Add your margin
    const margin = 0.03; // 3% edge
    const makerProbability = combinedProbability + margin;

    // Convert to SX protocol format (maker's percentageOdds)
    // percentageOdds = maker's implied probability * 10^20
    const percentageOdds = BigInt(Math.round(makerProbability * 1e20));

    return percentageOdds.toString();
  }

  function estimateLegProbability(leg) {
    // Replace with your actual pricing model
    // This is just a placeholder
    return 0.5;
  }
  ```

  ```python Python theme={null}
  ODDS_PRECISION = 10 ** 20

  def calculate_parlay_odds(leg_data):
      # Simple approach: multiply implied probabilities of each leg
      combined_probability = 1.0

      for leg in leg_data:
          leg_probability = estimate_leg_probability(leg)
          combined_probability *= leg_probability

      # Add your margin
      margin = 0.03  # 3% edge
      maker_probability = combined_probability + margin

      # Convert to SX protocol format
      percentage_odds = str(int(maker_probability * ODDS_PRECISION))
      return percentage_odds

  def estimate_leg_probability(leg):
      # Replace with your actual pricing model
      return 0.5
  ```
</CodeGroup>

<Info>
  The `percentageOdds` field represents the **maker's** implied probability in the SX protocol format (multiply by 10^20). The taker's implied odds are `1 - percentageOdds / 10^20`. See [odds formats](/developers/odds-formats) and [unit conversion](/api-reference/unit-conversion) for details.
</Info>

## Step 5: Post your order

Post an order to the parlay `marketHash` using [`POST /orders/new`](/api-reference/post-new-order), the same endpoint as single bets. The parlay market hash from the request is your `marketHash`. The `executor` address is available from [`GET /metadata`](/api-reference/get-metadata).

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Wallet, solidityPackedKeccak256, getBytes, randomBytes, hexlify } from "ethers";

  async function postParlayOrder(parlayMarketHash, baseToken, percentageOdds) {
    const wallet = new Wallet(process.env.SX_PRIVATE_KEY);

    const order = {
      marketHash: parlayMarketHash,
      maker: wallet.address,
      baseToken: baseToken,
      totalBetSize: "1000000000", // your offered size in base token units
      percentageOdds: percentageOdds,
      expiry: 2209006800,
      apiExpiry: Math.floor(Date.now() / 1000) + 60, // 1 minute from now
      executor: "0x...", // from GET /metadata → executorAddress
      salt: BigInt(hexlify(randomBytes(32))).toString(),
      isMakerBettingOutcomeOne: false, // maker takes the opposite side
    };

    const orderHash = getBytes(
      solidityPackedKeccak256(
        ["bytes32", "address", "uint256", "uint256", "uint256", "uint256", "address", "address", "bool"],
        [order.marketHash, order.baseToken, order.totalBetSize, order.percentageOdds, order.expiry, order.salt, order.maker, order.executor, order.isMakerBettingOutcomeOne]
      )
    );
    const signature = await wallet.signMessage(orderHash);

    const response = await fetch(`${BASE_URL}/orders/new`, {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-Api-Key": API_KEY },
      body: JSON.stringify({ orders: [{ ...order, signature }] }),
    }).then((r) => r.json());

    console.log("Order posted:", response);
  }
  ```

  ```python Python theme={null}
  import secrets
  from eth_account import Account
  from eth_account.messages import encode_defunct
  from web3 import Web3

  def post_parlay_order(parlay_market_hash, base_token, percentage_odds):
      account = Account.from_key(os.environ["SX_PRIVATE_KEY"])

      order = {
          "marketHash": parlay_market_hash,
          "maker": account.address,
          "baseToken": base_token,
          "totalBetSize": "1000000000",  # your offered size
          "percentageOdds": percentage_odds,
          "expiry": 2209006800,
          "apiExpiry": int(time.time()) + 60,  # 1 minute from now
          "executor": "0x...",  # from GET /metadata → executorAddress
          "salt": str(int.from_bytes(secrets.token_bytes(32), "big")),
          "isMakerBettingOutcomeOne": False,
      }

      order_hash = Web3.solidity_keccak(
          ["bytes32", "address", "uint256", "uint256", "uint256", "uint256", "address", "address", "bool"],
          [
              order["marketHash"],
              order["baseToken"],
              int(order["totalBetSize"]),
              int(order["percentageOdds"]),
              order["expiry"],
              int(order["salt"]),
              order["maker"],
              order["executor"],
              order["isMakerBettingOutcomeOne"],
          ]
      )
      message = encode_defunct(primitive=order_hash)
      signed = account.sign_message(message)
      signature = "0x" + signed.signature.hex()

      response = requests.post(
          f"{BASE_URL}/orders/new",
          json={"orders": [{**order, "signature": signature}]},
          headers={"X-Api-Key": API_KEY},
      )
      print("Order posted:", response.json())
  ```
</CodeGroup>

<Warning>You have a **3-second window** from when the parlay request is broadcast to post your order.</Warning>

## Step 6: Manage your orders

After posting, you can [cancel](/api-reference/post-cancel-orders) parlay orders the same way as any other order:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { hexlify, randomBytes } from "ethers";

  // Cancel a specific parlay order
  const salt = hexlify(randomBytes(32));
  const timestamp = Math.floor(Date.now() / 1000);
  const chainId = 4162; // Mainnet — use 79479957 for testnet

  const cancelSig = await wallet.signTypedData(
    { name: "CancelOrderV2SportX", version: "1.0", chainId, salt },
    { Details: [{ name: "orderHashes", type: "string[]" }, { name: "timestamp", type: "uint256" }] },
    { orderHashes: [orderHash], timestamp },
  );

  const response = await fetch(`${BASE_URL}/orders/cancel/v2`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ orderHashes: [orderHash], signature: cancelSig, salt, maker: wallet.address, timestamp }),
  }).then((r) => r.json());
  ```

  ```python Python theme={null}
  import secrets, time
  from eth_account import Account

  # Cancel a specific parlay order
  cancel_salt = "0x" + secrets.token_bytes(32).hex()
  timestamp = int(time.time())
  chain_id = 4162  # Mainnet — use 79479957 for testnet

  signed = Account.sign_typed_data(
      account.key,
      domain_data={"name": "CancelOrderV2SportX", "version": "1.0", "chainId": chain_id, "salt": cancel_salt},
      message_types={"Details": [{"name": "orderHashes", "type": "string[]"}, {"name": "timestamp", "type": "uint256"}]},
      message_data={"orderHashes": [order_hash], "timestamp": timestamp},
  )
  response = requests.post(
      f"{BASE_URL}/orders/cancel/v2",
      json={"orderHashes": [order_hash], "signature": "0x" + signed.signature.hex(), "salt": cancel_salt, "maker": account.address, "timestamp": timestamp},
  )
  ```
</CodeGroup>

## Summary

The full maker flow at a glance:

| Step | Action                                       | Time constraint |
| ---- | -------------------------------------------- | --------------- |
| 1    | Subscribe to `parlay_markets:global` channel | Once at startup |
| 2    | Receive parlay request with legs             | —               |
| 3    | Look up each leg's market data               | Within 3s       |
| 4    | Calculate combined odds + your margin        | Within 3s       |
| 5    | Post order to the parlay `marketHash`        | Within 3s       |
| 6    | Optionally cancel if not filled              | Anytime         |

<Tip>
  Parlay orders expire like normal orders. Set `apiExpiry` appropriately — a short expiry (e.g., 60 seconds) is recommended since parlay orderbooks are ephemeral.
</Tip>

## Related

<CardGroup cols={2}>
  <Card title="Parlays (RFQ System) →" icon="arrow-left" href="/developers/parlays">
    How the RFQ parlay system works conceptually.
  </Card>

  <Card title="Post New Order →" icon="book" href="/api-reference/post-new-order">
    API reference for the order submission endpoint.
  </Card>

  <Card title="Order Signing →" icon="key" href="/api-reference/eip712-signing">
    How to sign orders for the SX Bet protocol.
  </Card>

  <Card title="WebSocket Parlay Requests →" icon="bolt" href="/api-reference/centrifugo-parlay-market-requests">
    WebSocket channel reference for parlay requests.
  </Card>
</CardGroup>
