Skip to main content

Overview

Filling an order means taking the other side of an existing maker order on the orderbook. As a taker, you get immediate execution at the price a maker has posted. SX Bet uses POST /orders/fill/v2 to submit fills. The exchange matches your fill against the best available orders after a short betting delay.

Prerequisites

  1. Create an account at sx.bet and export your private key from the assets page
  2. Fund your account with USDC
  3. Enable betting by approving the TokenTransferProxy contract — see Enabling Betting

Fill fields

Each fill submitted to POST /orders/fill/v2 requires these fields:
FieldTypeDescription
marketstringUser-facing string for the market. Set to "N/A" when using the API
baseTokenstringThe token this fill is denominated in (e.g., USDC). See References for token addresses
isTakerBettingOutcomeOnebooleantrue if you’re betting on outcome one, false for outcome two
stakeWeistringYour stake in base token units (Ethereum format). See Unit Conversion
desiredOddsstringThe worst taker odds you’ll accept, in SX protocol format (implied * 10^20)
oddsSlippageintegerPercentage tolerance (0–100) applied to desiredOdds. 0 means exact odds only
fillSaltstringA random 32-byte value to identify this fill
takerstringYour wallet address
takerSigstringYour wallet signature over the fill payload
messagestringA user-facing message included in the signing payload. Can be anything (e.g., "N/A")

Understanding desiredOdds

This is the implied probability from your perspective as the taker. It’s the inverse of the maker’s percentageOdds:
taker_implied = 10^20 - maker_percentageOdds
For example, if the best maker order on outcome two has percentageOdds = "52500000000000000000" (52.5%), the taker odds for betting outcome one are:
10^20 - 52500000000000000000 = 47500000000000000000 (47.5%)
You’d set desiredOdds = "47500000000000000000" to fill at that price.
Orders with taker odds better than your desiredOdds will also fill. The desiredOdds is a floor, not an exact match.

Understanding oddsSlippage

The oddsSlippage field is an integer from 0 to 100 representing a percentage tolerance on your desiredOdds. This is useful for volatile in-play markets where odds shift quickly.
  • 0 — only accept desiredOdds or better (no slippage)
  • 5 — accept up to 5% worse than desiredOdds
For pre-game markets, 0 is usually fine. For in-play markets, a small slippage (2–5) helps ensure your fill goes through.

Understanding stakeWei

This is the amount of tokens you are putting up for the bet. It’s in Ethereum units — for USDC (6 decimals), "50000000" = 50 USDC. If there isn’t enough liquidity to fill your full stake, the exchange will partially fill you for the available amount.
The minimum taker stake is 1 USDC.

Step 1: Find orders to fill

Check what odds are available on the market you want to bet on using GET /orders/odds/best:
import requests

BASE_URL = "https://api.sx.bet"  # Mainnet — use https://api.toronto.sx.bet for testnet
ODDS_PRECISION = 10 ** 20

market_hash = "YOUR_MARKET_HASH"
base_token = "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B"  # Mainnet USDC — see References for testnet address

best = requests.get(
    f"{BASE_URL}/orders/odds/best",
    params={"marketHashes": market_hash, "baseToken": base_token}
).json()["data"]["bestOdds"][0]

o1_maker = int(best["outcomeOne"]["percentageOdds"]) / ODDS_PRECISION
o2_maker = int(best["outcomeTwo"]["percentageOdds"]) / ODDS_PRECISION

# Taker odds are the inverse of the opposite outcome's maker odds
print(f"Taker odds for outcome 1: {1 - o2_maker:.2%}")
print(f"Taker odds for outcome 2: {1 - o1_maker:.2%}")
For deeper orderbook inspection, use GET /orders to see all open orders and their available sizes. See Navigating the Orderbook for more on reading depth.

Step 2: Sign and submit the fill

Fills require a typed data signature from your wallet. This is a different signing method from maker orders — the code below handles it for you. See Order Signing for the full reference.
The verifyingContract, chainId, and baseToken values below are hardcoded to mainnet. Using the wrong values will cause signature failures. Fetch verifyingContract from GET /metadataEIP712FillHasher, and get the correct baseToken for your network from References. See Testnet & Mainnet for the recommended config pattern.
import os
import secrets
import requests
from eth_account import Account

BASE_URL = "https://api.sx.bet"  # Mainnet — use https://api.toronto.sx.bet for testnet
account = Account.from_key(os.environ["SX_PRIVATE_KEY"])

market_hash = "YOUR_MARKET_HASH"
base_token = "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B"  # Mainnet USDC — see References for testnet address
stake_wei = "50000000"  # 50 USDC (6 decimals)

# Taker odds: 10^20 - best maker odds on the opposite outcome
desired_odds = "47500000000000000000"  # 47.5% taker implied

fill_salt = int.from_bytes(secrets.token_bytes(32), "big")

# --- Sign the fill ---
DOMAIN = {
    "name": "SX Bet",
    "version": "6.0",
    "chainId": 4162,
    "verifyingContract": "0x845a2Da2D70fEDe8474b1C8518200798c60aC364",  # Mainnet — use GET /metadata → EIP712FillHasher for testnet
}

FILL_TYPES = {
    "Details": [
        {"name": "action", "type": "string"},
        {"name": "market", "type": "string"},
        {"name": "betting", "type": "string"},
        {"name": "stake", "type": "string"},
        {"name": "worstOdds", "type": "string"},
        {"name": "worstReturning", "type": "string"},
        {"name": "fills", "type": "FillObject"},
    ],
    "FillObject": [
        {"name": "stakeWei", "type": "string"},
        {"name": "marketHash", "type": "string"},
        {"name": "baseToken", "type": "string"},
        {"name": "desiredOdds", "type": "string"},
        {"name": "oddsSlippage", "type": "uint256"},
        {"name": "isTakerBettingOutcomeOne", "type": "bool"},
        {"name": "fillSalt", "type": "uint256"},
        {"name": "beneficiary", "type": "address"},
        {"name": "beneficiaryType", "type": "uint8"},
        {"name": "cashOutTarget", "type": "bytes32"},
    ],
}

signed = Account.sign_typed_data(
    account.key,
    domain_data=DOMAIN,
    message_types=FILL_TYPES,
    message_data={
        "action": "N/A",
        "market": market_hash,
        "betting": "N/A",
        "stake": "N/A",
        "worstOdds": "N/A",
        "worstReturning": "N/A",
        "fills": {
            "stakeWei": stake_wei,
            "marketHash": market_hash,
            "baseToken": base_token,
            "desiredOdds": desired_odds,
            "oddsSlippage": 0,
            "isTakerBettingOutcomeOne": True,
            "fillSalt": fill_salt,
            "beneficiary": "0x0000000000000000000000000000000000000000",
            "beneficiaryType": 0,
            "cashOutTarget": b"\x00" * 32,
        },
    },
)
taker_sig = "0x" + signed.signature.hex()

# --- Submit ---
response = requests.post(f"{BASE_URL}/orders/fill/v2", json={
    "market": market_hash,
    "baseToken": base_token,
    "isTakerBettingOutcomeOne": True,
    "stakeWei": stake_wei,
    "desiredOdds": desired_odds,
    "oddsSlippage": 0,
    "taker": account.address,
    "takerSig": taker_sig,
    "fillSalt": str(fill_salt),
})

result = response.json()
print("Status:", result["status"])
print("Data:", result["data"])

How filling works

After you submit a fill:
  1. The exchange queues your fill and applies a short betting delay (a few seconds for pre-game, optimized for in-play)
  2. After the delay, the exchange matches your fill against the best available maker orders at or better than your desiredOdds (accounting for oddsSlippage)
  3. If enough liquidity exists, your fill executes. If not, you get a partial fill for the available amount
  4. The resulting trade(s) appear in your trade history
The betting delay prevents front-running on in-play markets where odds shift rapidly. Your fill is matched against the orderbook state after the delay, not at submission time.

Monitoring your fills

Subscribe to the recent_trades:global WebSocket channel to get real-time notifications when fills execute. This is a global feed — filter messages to your address in the handler:
import { Centrifuge } from "centrifuge";

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": process.env.SX_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 userAddress = wallet.address;

const sub = client.newSubscription("recent_trades:global", {
  positioned: true,
  recoverable: true,
});

sub.on("publication", (ctx) => {
  const trades = Array.isArray(ctx.data) ? ctx.data : [ctx.data];
  for (const trade of trades) {
    if (trade.bettor.toLowerCase() !== userAddress.toLowerCase()) continue;
    console.log(`Trade: market=${trade.marketHash}, stake=${trade.stake}, odds=${trade.odds}`);
  }
});

sub.subscribe();
client.connect();
You can also poll your trade history via GET /trades with the bettor parameter.

Real-time orderbook and odds

If you’re actively scanning markets before filling — especially in-play — polling REST adds latency. Two WebSocket channels are useful here:
  • order_book:market_{marketHash} — full orderbook updates for a specific market. Subscribe with positioned: true, recoverable: true before fetching the snapshot to avoid gaps. See Fetching Odds → Real-time orderbook for the correct subscribe-then-fetch pattern.
  • best_odds:global — fires whenever the best available odds change across any market. Useful for watching multiple markets without subscribing to each orderbook individually. See Fetching Odds → Real-time best odds.

Common issues

Fill rejected — betting not enabled

You need to approve the TokenTransferProxy contract for each token you trade. See Enabling Betting.

Fill rejected — insufficient balance

Your wallet must have enough of the base token to cover stakeWei. Fund your account before submitting.

Fill returns no trades

This means there wasn’t enough liquidity at your desiredOdds (accounting for oddsSlippage). Either relax your odds, increase slippage, or wait for more maker orders to appear on the book.

In-play fills timing out

For in-play markets, odds move quickly. Use a small oddsSlippage (2–5) to give your fill a better chance of executing. Check the latest odds via GET /orders/odds/best before each fill.

Order Signing →

Full signing reference for fills and orders.

POST /orders/fill/v2 →

Full API reference for the fill endpoint.

Navigating the Orderbook →

Reading depth and finding the best prices.

Unit Conversion →

Converting between raw values and human-readable amounts.