Skip to main content

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 to receive parlay requests in real-time. You’ll need a token from the GET /user/realtime-token/api-key endpoint.
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();

Step 2: Parse the parlay request

Each incoming message contains the parlay’s marketHash, the requested token and size, and the legs:
{
  "marketHash": "0x38cceead7bda65c18574a34994ebd8af154725d08aa735dcbf26247a7dcc67bd",
  "baseToken": "0x8f3Cf7ad23Cd3CaDb09735AFf958023239c6A063",
  "requestSize": "100000000",
  "legs": [
    {
      "marketHash": "0x0d64c52e8781acdada86920a2d1e5acd6f29dcfe285cf9cae367b671dff05f7d",
      "bettingOutcomeOne": true
    },
    {
      "marketHash": "0xe609a49d083cd41214a0db276c1ba323c4a947eefd2e4260386fec7b5d258188",
      "bettingOutcomeOne": false
    }
  ]
}
FieldDescription
marketHashThe parlay market to post your order against
baseTokenToken the bettor wants to bet in
requestSizeRequested size in base token units (see unit conversion)
legsArray of individual markets and the outcomes the bettor selected
The requestSize is what the bettor requested, but you can offer any size you want. You are not limited to matching their request.

Step 3: Look up each leg

Query each leg’s market via GET /markets/find to understand what’s being bet on, and check current odds via GET /orders/odds/best:
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);
  }
}

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:
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;
}
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 and unit conversion for details.

Step 5: Post your order

Post an order to the parlay marketHash using POST /orders/new, the same endpoint as single bets. The parlay market hash from the request is your marketHash. The executor address is available from GET /metadata.
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);
}
You have a 3-second window from when the parlay request is broadcast to post your order.

Step 6: Manage your orders

After posting, you can cancel parlay orders the same way as any other order:
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());

Summary

The full maker flow at a glance:
StepActionTime constraint
1Subscribe to parlay_markets:global channelOnce at startup
2Receive parlay request with legs
3Look up each leg’s market dataWithin 3s
4Calculate combined odds + your marginWithin 3s
5Post order to the parlay marketHashWithin 3s
6Optionally cancel if not filledAnytime
Parlay orders expire like normal orders. Set apiExpiry appropriately — a short expiry (e.g., 60 seconds) is recommended since parlay orderbooks are ephemeral.

Parlays (RFQ System) →

How the RFQ parlay system works conceptually.

Post New Order →

API reference for the order submission endpoint.

Order Signing →

How to sign orders for the SX Bet protocol.

WebSocket Parlay Requests →

WebSocket channel reference for parlay requests.