What you’ll need
- An account on SX Bet (for signing orders)
- USDC (to fill an order)
Market data and orderbook reads are public — no authentication needed. You only need an account for posting or filling orders.
Base URL
All API requests go to:https://api.sx.bet
Install dependencies
pip install requests eth-account python-dotenv
npm install ethers dotenv
Create an account
Sign up at sx.bet with email or Google. Complete registration by choosing a username.Fetch your private key from the assets page. You will need this to sign orders through the API.Store it securely in a
.env file:SX_PRIVATE_KEY="0xyour_private_key_here"
Fetch a market
Fetch active markets to find one you want to bet on. Copy the
marketHash — you’ll need it in the next steps.curl "https://api.sx.bet/markets/active"
import requests
BASE_URL = "https://api.sx.bet" # Mainnet — use https://api.toronto.sx.bet for testnet
markets = requests.get(
f"{BASE_URL}/markets/active"
).json()["data"]["markets"]
first = markets[0]
print("Market hash:", first["marketHash"])
print("Event:", first["teamOneName"], "vs", first["teamTwoName"])
print(f"Outcomes: [1 = {first['outcomeOneName']}, 2 = {first['outcomeTwoName']}]")
print("Start time:", first["gameTime"])
const BASE_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
const data = await fetch(`${BASE_URL}/markets/active`).then((r) => r.json());
const markets = data.data.markets;
const first = markets[0];
console.log("Market hash:", first.marketHash);
console.log("Event:", first.teamOneName, "vs", first.teamTwoName);
console.log(`Outcomes: [1 = ${first.outcomeOneName}, 2 = ${first.outcomeTwoName}]`);
console.log("Start time:", first.gameTime);
Fetch current odds
Get the best available odds for your market (from the maker’s perspective) using the
marketHash from the previous step. You’ll use these to derive your taker odds in the next step.curl "https://api.sx.bet/orders/odds/best?marketHashes=YOUR_MARKET_HASH&baseToken=0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B"
market_hash = "YOUR_MARKET_HASH" # from step 2
response = requests.get(
f"{BASE_URL}/orders/odds/best",
params={
"marketHashes": market_hash,
"baseToken": "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B", # Mainnet USDC — see References for testnet address
}
)
odds = response.json()["data"]["bestOdds"][0]
# Maker odds — the best prices available from makers on each outcome
# percentageOdds is null if no orders exist on that side
pct_one = odds["outcomeOne"]["percentageOdds"]
pct_two = odds["outcomeTwo"]["percentageOdds"]
outcome_one_maker = int(pct_one) / 10**20 if pct_one is not None else None
outcome_two_maker = int(pct_two) / 10**20 if pct_two is not None else None
print(f"Maker odds on outcome 1: {outcome_one_maker:.2%}" if outcome_one_maker is not None else "Maker odds on outcome 1: no orders")
print(f"Maker odds on outcome 2: {outcome_two_maker:.2%}" if outcome_two_maker is not None else "Maker odds on outcome 2: no orders")
const BASE_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
const marketHash = "YOUR_MARKET_HASH"; // from step 2
const response = await fetch(
`${BASE_URL}/orders/odds/best?${new URLSearchParams({
marketHashes: marketHash,
baseToken: "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B", // Mainnet USDC — see References for testnet address
})}`
).then((r) => r.json());
const odds = response.data.bestOdds[0];
// Maker odds — the best prices available from makers on each outcome
// percentageOdds is null if no orders exist on that side
const pctOne = odds.outcomeOne.percentageOdds;
const pctTwo = odds.outcomeTwo.percentageOdds;
const outcomeOneMaker = pctOne !== null ? Number(pctOne) / 1e20 : null;
const outcomeTwoMaker = pctTwo !== null ? Number(pctTwo) / 1e20 : null;
console.log(outcomeOneMaker !== null ? `Maker odds on outcome 1: ${(outcomeOneMaker * 100).toFixed(2)}%` : "Maker odds on outcome 1: no orders");
console.log(outcomeTwoMaker !== null ? `Maker odds on outcome 2: ${(outcomeTwoMaker * 100).toFixed(2)}%` : "Maker odds on outcome 2: no orders");
Place a bet
Submit a fill against the market from step 2.
desiredOdds is from the taker’s perspective — the inverse of the maker’s best odds on the opposite outcome from step 3.The
verifyingContract and baseToken values below are hardcoded to mainnet. Using the wrong values will cause signature failures. Fetch verifyingContract from GET /metadata → EIP712FillHasher, 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" # from step 2
base_token = "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B" # Mainnet USDC — see References for testnet address
stake_wei = "50000000" # 50 USDC (6 decimals)
# Desired odds (taker's perspective)
# To get best available taker odds: 10^20 - opposite outcome's maker odds from step 3
desired_odds = "YOUR_DESIRED_ODDS"
is_taker_betting_outcome_one = True # True = bet outcome 1, False = bet outcome 2
fill_salt = int.from_bytes(secrets.token_bytes(32), "big")
# --- Sign the fill ---
DOMAIN = {
"name": "SX Bet",
"version": "6.0",
"chainId": 4162, # Mainnet — use 79479957 for testnet
"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": is_taker_betting_outcome_one,
"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": is_taker_betting_outcome_one,
"stakeWei": stake_wei,
"desiredOdds": desired_odds,
"oddsSlippage": 0,
"taker": account.address,
"takerSig": taker_sig,
"fillSalt": str(fill_salt),
})
print("Fill result:", response.json())
import "dotenv/config";
import { Wallet, ZeroAddress, ZeroHash, randomBytes, hexlify } from "ethers";
const BASE_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
const wallet = new Wallet(process.env.SX_PRIVATE_KEY);
const marketHash = "YOUR_MARKET_HASH"; // from step 2
const baseToken = "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B"; // Mainnet USDC — see References for testnet address
const stakeWei = "50000000"; // 50 USDC (6 decimals)
// Desired odds (taker's perspective)
// To get best available taker odds: 10^20 - opposite outcome's maker odds from step 3
const desiredOdds = "YOUR_DESIRED_ODDS";
const oddsSlippage = 0;
const isTakerBettingOutcomeOne = true; // true = bet outcome 1, false = bet outcome 2
const fillSalt = BigInt(hexlify(randomBytes(32))).toString();
// --- Sign the fill ---
const domain = {
name: "SX Bet",
version: "6.0",
chainId: 4162, // Mainnet — use 79479957 for testnet
verifyingContract: "0x845a2Da2D70fEDe8474b1C8518200798c60aC364", // Mainnet — use GET /metadata → EIP712FillHasher for testnet
};
const 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" },
],
};
const message = {
action: "N/A",
market: marketHash,
betting: "N/A",
stake: "N/A",
worstOdds: "N/A",
worstReturning: "N/A",
fills: {
stakeWei,
marketHash,
baseToken,
desiredOdds,
oddsSlippage,
isTakerBettingOutcomeOne,
fillSalt,
beneficiary: ZeroAddress,
beneficiaryType: 0,
cashOutTarget: ZeroHash,
},
};
const takerSig = await wallet.signTypedData(domain, types, message);
// --- Submit ---
const response = await fetch(`${BASE_URL}/orders/fill/v2`, {
method: "POST",
body: JSON.stringify({
market: marketHash,
baseToken,
isTakerBettingOutcomeOne,
stakeWei,
desiredOdds,
oddsSlippage,
taker: wallet.address,
takerSig,
fillSalt,
}),
headers: { "Content-Type": "application/json" },
});
console.log("Fill result:", await response.json());
If your fill returns
"status": "success", you’re live on mainnet. Check your open positions at sx.bet/my-bets.Core Concepts
Understand how the exchange and orderbook work in depth.
Filling Orders
Step-by-step guide to signing and submitting fills.
