NAV

Introduction

Be your own bookmaker or fill orders programmatically with the SX.bet API!

Technical questions or need support? Send us an e-mail.

All documentation is provided in JavaScript.

SX Rollup Migration Guide

What is this migration?

The current version of SX Network was built on the Polygon Edge SDK. This project has since been discontinued, and so there is no support or improvements coming for the chain. For a number of reasons including improving block times, making use of a canonical bridge, better stability and developer support, we have decided to migrate our chain to an Arbitrum Orbit Rollup chain.

You can read more about the rationale here.

What do I have to do?

When the sport you're interested in has moved over to SXR, you'll have to:

Endpoint Changes

The following endpoints allow a new query parameter: chainVersion. This will filter the API response to only the specific chain you are looking for. If no parameter is passed, there is a non-intrusive default used.

This ensures the changes are least disruptive, and changes are only required when handling games on the new chain.

Allowed values for chainVersion are:

chainVersion Description
SXN Legacy SX Network
SXR SX Rollup

SXR Metadata

Get SXR Metadata

GET https://api.sx.bet/metadata?chainVersion=SXR

SXR Leagues

Get Active SXR Leagues

GET https://api.sx.bet/leagues/active?chainVersion=SXR

SXR Markets

Get Active SXR Markets

GET https://api.sx.bet/markets/active?chainVersion=SXR

GET https://api.sx.bet/markets/popular?chainVersion=SXR

SXR Trades

Get SXR Trades

GET https://api.sx.bet/trades?chainVersion=SXR

SXR Consolidated Trades

Get SXR Consolidated Trades

GET https://api.sx.bet/trades/consolidated?chainVersion=SXR

SXR Orders

Get SXR Orders

GET https://api.sx.bet/orders?chainVersion=SXR

Cancel SXR Orders

POST https://api.sx.bet/orders/cancel/v2?chainVersion=SXR

Cancel SXR Orders by Event

POST https://api.sx.bet/orders/cancel/event?chainVersion=SXR

Cancel all SXR Orders

POST https://api.sx.bet/orders/cancel/all?chainVersion=SXR

Sport Migration Schedule

Our plan is to migrate by sport to reduce impact as much as possible. Our migration by sport schedule is as follows:

Sport(s) Date
E Sports, Novelty Markets, Rugby, Politics, Entertainment, NFTs, Daily Parlays July 29, 2024
Boxing, Olympics, Racing, Cricket July 30, 2024
Football, Hockey, Basketball July 31, 2024
Baseball, Golf, Soccer, Tennis, Mixed Martial Arts August 5, 2024
Crypto, Degen Crypto August 6, 2024

References

SXN = Legacy SX Network
SXR = New SX Network

SXR Mainnet

Reference Type Data
Chain ID 4162
USDC Token Address 0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B
WSX Token Address 0x3E96B0a25d51e3Cc89C557f152797c33B839968f
SX Application https://sx.bet
SX API URL https://api.sx.bet
RPC URL https://rpc.sx-rollup.gelato.digital
Explorer Page https://explorerl2.sx.technology (this will be replaced by the current explorer domain once migration is fully complete)
METADATA https://api.sx.bet/metadata?chainVersion=SXR

SXR Testnet

Reference Type Data
Chain ID 79479957
USDC Token Address 0x1BC6326EA6aF2aB8E4b6Bc83418044B1923b2956
WSX Token Address 0x5c02932f8422D943647682E95c87ea0333191e08
SX Application https://toronto.sx.bet
SX API URL https://api.toronto.sx.bet
RPC URL https://rpc.sx-rollup-testnet.t.raas.gelato.cloud
Explorer Page https://explorerl2.toronto.sx.technology (this will be replaced by the current explorer domain once migration is fully complete)
METADATA https://api.toronto.sx.bet/metadata?chainVersion=SXR

SXN Mainnet

Reference Type Data
Chain ID 416
USDC Token Address 0xe2aa35C2039Bd0Ff196A6Ef99523CC0D3972ae3e
WSX Token Address 0xaa99bE3356a11eE92c3f099BD7a038399633566f
SX Application https://sx.bet
SX API URL https://api.sx.bet
RPC URL https://rpc.sx.technology
Explorer Page https://explorer.sx.technology
METADATA https://api.sx.bet/metadata?chainVersion=SXN

SXN Testnet

Reference Type Data
Chain ID 647
USDC Token Address 0x5147891461a7C81075950f8eE6384e019e39ab98
WSX Token Address 0x2D4e10Ee64CCF407C7F765B363348f7F62D2E06e
SX Application https://toronto.sx.bet
SX API URL https://api.toronto.sx.bet
RPC URL https://rpc.toronto.sx.technology
Explorer Page https://explorer.toronto.sx.technology
METADATA https://api.toronto.sx.bet/metadata?chainVersion=SXN

Versions of dependencies

These are the latest versions of js dependencies that are referenced in documentation:

Testnet

We have deployed a testnet chain and application (https://toronto.sx.bet).

See configuration for testnet here.

Contact the team at api-support@sx.bet to receive testnet funds.

Metadata

Get metadata

curl --location --request GET 'https://api.sx.bet/metadata'

The above command returns JSON structured like this (this just a sample, visit the link for upto date info)

{
  "status": "success",
  "data": {
    "executorAddress": "0x52adf738AAD93c31f798a30b2C74D658e1E9a562",
    "oracleFees": {
      "0xA173954Cc4b1810C0dBdb007522ADbC182DaB380": "2600000000000000000",
      "0xe2aa35C2039Bd0Ff196A6Ef99523CC0D3972ae3e": "2600000000000000000",
      "0xaa99bE3356a11eE92c3f099BD7a038399633566f": "2600000000000000000"
    },
    "sportXAffiliate": {
      "address": "0xa21ac1436f7fcD43008c9473A78433339E222FcA",
      "amount": "1400000000000000000"
    },
    "makerOrderMinimums": {
      "0xA173954Cc4b1810C0dBdb007522ADbC182DaB380": "5000000000000000",
      "0xe2aa35C2039Bd0Ff196A6Ef99523CC0D3972ae3e": "10000000",
      "0xaa99bE3356a11eE92c3f099BD7a038399633566f": "30000000000000000000"
    },
    "takerMinimums": {
      "0xA173954Cc4b1810C0dBdb007522ADbC182DaB380": "2500000000000000",
      "0xe2aa35C2039Bd0Ff196A6Ef99523CC0D3972ae3e": "5000000",
      "0xaa99bE3356a11eE92c3f099BD7a038399633566f": "30000000000000000000"
    },
    "addresses": {
      "416": {
        "WETH": "0xA173954Cc4b1810C0dBdb007522ADbC182DaB380",
        "USDC": "0xe2aa35C2039Bd0Ff196A6Ef99523CC0D3972ae3e",
        "WSX": "0xaa99bE3356a11eE92c3f099BD7a038399633566f"
      }
    },
    "totalVolume": 214120305.80276245,
    "domainVersion": "5.0",
    "EIP712FillHasher": "0x3E96B0a25d51e3Cc89C557f152797c33B839968f",
    "TokenTransferProxy": "0xCc4fBba7D0E0F2A03113F42f5D3aE80d9B2aD55d",
    "bridgeFee": 1,
    "oddsLadderStepSize": 25
  }
}

This endpoint retrieves metadata on the exchange itself and useful parameters to interact with the exchange.

HTTP Request

GET https://api.sx.bet/metadata

Query parameters

Name Required Type Description
chainVersion false string Must be either SXN or SXR.
Default is SXN. See migration docs

Response format

Name Type Description
status string success or failure if the request succeeded or not
data any The metadata for the exchange
executorAddress string The executor address for the sx.bet exchange. Use this address for setting the executor when posting new orders as a market maker
oracleFees any A mapping from token address to a percentage indicating the fees paid on profit for each token. To convert to a readable number, divide by 10^20
sportXAffiliate any The default sx.bet affiliate. Currently set to 0 and unused
makerOrderMinimums any A mapping from token address to a real token amount indicating the minimum maker order size. To convert into a readable amount, see the token conversion section
takerMinimums any A mapping from token address to a real token amount indicating the minimum taker order size. To convert into a readable amount, see the token conversion section
addresses any A mapping from network id -> canonical token name -> token address of assets supported by the exchange
totalVolume number All time total volume on the exchange
domainVersion string Used in EIP712 signing
EIP712FillHasher string Address used in EIP712 signing for filling orders
TokenTransferProxy string Address used in EIP712 signing for enabling betting
oddsLadderStepSize number Odds ladder step size. See the post a new order section

Websocket API

You must have a valid API key to subscribe to realtime channels via the API. See API Key for more info.

You can connect to the websocket API and listen for realtime changes on several resources such as the order book, markets, scores, and line updates.

Initialization

import * as ably from "ably";
import axios from "axios";

async function createTokenRequest() {
  const response = await axios.get("https://api.sx.bet/user/token", {
    headers: {
      "X-Api-Key": process.env.SX_BET_API_KEY,
    },
  });
  return response.data;
}

async function initialize() {
  const realtime = new ably.Realtime.Promise({
    authCallback: async (tokenParams, callback) => {
      try {
        const tokenRequest = await createTokenRequest();
        // Make a network request to GET /user/token passing in
        // `X-Api-Key: [YOUR_API_KEY]` as a header
        callback(null, tokenRequest);
      } catch (error) {
        callback(error, null);
      }
    },
  });
  await ablyClient.connection.once("connected");
}

We use the Ably SDK to allow users to connect to our API. It supports pretty much every major language but all of the examples on this page will be in JavaScript. The API is relatively identical across languages though. See this link for a basic overview of the API in other languages.

All the examples following assume you have a realtime object in scope following the initialization code to the right.

Market updates

const channel = realtime.channels.get(`markets`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this:

[
  {
    "gameTime": 1625674200,
    "group1": "MLB",
    "leagueId": 171,
    "leagueLabel": "MLB",
    "line": 7,
    "liveEnabled": false,
    "marketHash": "0x384c6d8e17c9b5522a17f7bb049ede7d3dd9dd1311232fe854e7f9f4708dfc4c",
    "outcomeOneName": "Over 7.0",
    "outcomeTwoName": "Under 7.0",
    "outcomeVoidName": "NO_GAME_OR_EVEN",
    "sportId": 3,
    "sportLabel": "Baseball",
    "sportXEventId": "L7186379",
    "status": "ACTIVE",
    "teamOneName": "Tampa Bay Rays",
    "teamTwoName": "Cleveland Indians",
    "type": 2
  }
]

Subscribe to all changes in markets on sx.bet. You will get updates when

Channel name format

markets

Message payload format

See the markets section for the format of the message

Parlay Market requests

const channel = realtime.channels.get(`markets:parlay`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this:

{
  "marketHash": "0x38cceead7bda65c18574a34994ebd8af154725d08aa735dcbf26247a7dcc67bd",
  "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
  "requestSize": "100000000",
  "legs": [
    {
      "marketHash": "0x0d64c52e8781acdada86920a2d1e5acd6f29dcfe285cf9cae367b671dff05f7d",
      "bettingOutcomeOne": true
    },
    {
      "marketHash": "0xe609a49d083cd41214a0db276c1ba323c4a947eefd2e4260386fec7b5d258188",
      "bettingOutcomeOne": false
    }
  ]
}

When a bettor requests a Parlay Market, a message is sent via the markets:parlay channel. In order to offer orders on Parlay Markets, you will need to subscribe to this channel. The payload will contain the marketHash that is associated with the Parlay Market.

You can post orders to this market as you would for any other market, using this marketHash. The payload also contains the token and size that the bettor is requesting. The legs in the payload contain the underlying legs that make up the parlay market. You can query for market data on each leg's marketHash to determine current orders for that individual market.

ParlayMarket payload format

Name Type Description
marketHash string The parlay market associated with this request
baseToken string The token this request is denominated in
requestSize number The size in baseTokens that the bettor is requesting. See the token section of how to convert this into nominal amounts
legs ParlayMarketLeg[] An array of legs that make up the parlay

ParlayMarketLeg payload format

Name Type Description
marketHash string The market for an individual leg within the parlay
bettingOutcomeOne boolean The side the bettor is betting for an individual leg within the parlay

Line changes

const channel = realtime.channels.get(`main_line`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this:

{
  "marketHash": "0x38cceead7bda65c18574a34994ebd8af154725d08aa735dcbf26247a7dcc67bd",
  "marketType": 3,
  "sportXEventId": "L7178624"
}

Subscribe to all line changes. Messages are sent for particular combinations of event IDs and market types. Note that only market types with lines will have updates sent. See the active markets section for details on which types have lines.

Channel name format

main_line

Message payload format

Name Type Description
marketHash string The market which is now the main line for this event ID
marketType number The type of market this update refers to.
sportXEventId string The event ID for this update

To get the actual line, you'll have to fetch the market using the marketHash

Live score updates

const sportXEventId = "L7178624";
const channel = realtime.channels.get(`live_scores:${sportXEventId}`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this:

{
  "teamOneScore": 2,
  "teamTwoScore": 1,
  "sportXEventId": "L7178624",
  "currentPeriod": "4th Set",
  "periodTime": "-1",
  "sportId": 6,
  "leagueId": 1263,
  "periods": [
    {
      "label": "1st Set",
      "isFinished": true,
      "teamOneScore": "4",
      "teamTwoScore": "6"
    },
    {
      "label": "2nd Set",
      "isFinished": true,
      "teamOneScore": "6",
      "teamTwoScore": "3"
    },
    {
      "label": "3rd Set",
      "isFinished": true,
      "teamOneScore": "7",
      "teamTwoScore": "5"
    },
    {
      "label": "4th Set",
      "isFinished": false,
      "teamOneScore": "1",
      "teamTwoScore": "2"
    },
    {
      "label": "Game",
      "isFinished": false,
      "teamOneScore": "0",
      "teamTwoScore": "0"
    }
  ],
  "extra": "[{\"Name\":\"Turn\",\"Value\":\"1\"},{\"Name\":\"DoubleFaults\",\"Value\":\"{\\\"3/6\\\":\\\"0,0,0,0,0,0\\\",\\\"3/7\\\":\\\"0,2,0,0,0,0\\\",\\\"3/4\\\":\\\"0,0,0,0,0\\\",\\\"3/5\\\":\\\"0,0,0,0\\\",\\\"1/4\\\":\\\"0,0,0,0\\\",\\\"3/2\\\":\\\"0,0,0,0,0,0\\\",\\\"1/5\\\":\\\"0,0,0,0,0\\\",\\\"3/3\\\":\\\"0,0,0,0,0\\\",\\\"1/6\\\":\\\"2,2,0,0,0,0,2,0,0,0\\\",\\\"1/7\\\":\\\"0,0,0,0,0,0\\\",\\\"3/12\\\":\\\"0,0,0,0,0,0,0\\\",\\\"3/1\\\":\\\"0,0,0,0,0\\\",\\\"1/1\\\":\\\"0,0,0,0,0\\\",\\\"1/2\\\":\\\"0,0,0,0,0\\\",\\\"3/11\\\":\\\"0,0,0,0,0,0,2,0\\\",\\\"1/3\\\":\\\"0,0,0,0,0\\\",\\\"3/8\\\":\\\"0,0,1,0,0,0,0,0,0,0\\\",\\\"1/8\\\":\\\"0,0,0,0,0\\\",\\\"3/9\\\":\\\"0,0,0,0,0\\\",\\\"1/9\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"2/7\\\":\\\"0,0,0,0,0,0\\\",\\\"2/6\\\":\\\"0,0,0,0,0\\\",\\\"2/5\\\":\\\"0,0,0,0,0,0,0,0,0,0\\\",\\\"2/4\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"2/3\\\":\\\"0,0,0,0\\\",\\\"2/2\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"2/1\\\":\\\"0,0,0,0,0\\\",\\\"4/1\\\":\\\"0,0,2,0,2,0\\\",\\\"4/3\\\":\\\"0,0,0,0,0\\\",\\\"4/2\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"3/10\\\":\\\"0,0,0,0,0,0\\\",\\\"2/9\\\":\\\"0,0,0,0,0\\\",\\\"1/10\\\":\\\"0,0,0,0\\\",\\\"2/8\\\":\\\"0,0,0,0\\\"}\"},{\"Name\":\"CourtSurfaceType\",\"Value\":\"Grass\"},{\"Name\":\"Aces\",\"Value\":\"{\\\"3/6\\\":\\\"0,0,0,0,0,0\\\",\\\"3/7\\\":\\\"0,0,0,0,0,0\\\",\\\"3/4\\\":\\\"0,0,0,0,0\\\",\\\"3/5\\\":\\\"0,2,0,0\\\",\\\"1/4\\\":\\\"2,2,0,0\\\",\\\"3/2\\\":\\\"0,0,0,0,0,0\\\",\\\"1/5\\\":\\\"0,0,0,0,0\\\",\\\"3/3\\\":\\\"0,0,0,0,2\\\",\\\"1/6\\\":\\\"0,0,0,0,0,0,0,0,0,0\\\",\\\"1/7\\\":\\\"0,0,0,0,0,0\\\",\\\"3/12\\\":\\\"1,0,0,0,0,0,0\\\",\\\"3/1\\\":\\\"0,0,0,2,0\\\",\\\"1/1\\\":\\\"0,0,0,0,0\\\",\\\"1/2\\\":\\\"0,2,0,0,0\\\",\\\"3/11\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"1/3\\\":\\\"0,0,0,0,0\\\",\\\"3/8\\\":\\\"0,0,0,0,0,0,0,0,0,0\\\",\\\"1/8\\\":\\\"0,0,0,0,0\\\",\\\"3/9\\\":\\\"0,0,0,0,0\\\",\\\"1/9\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"2/7\\\":\\\"0,0,0,0,0,0\\\",\\\"2/6\\\":\\\"0,0,0,0,0\\\",\\\"2/5\\\":\\\"0,0,0,0,0,0,0,0,0,0\\\",\\\"2/4\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"2/3\\\":\\\"0,0,0,0\\\",\\\"2/2\\\":\\\"0,0,0,0,0,0,0,0\\\",\\\"2/1\\\":\\\"0,0,0,0,0\\\",\\\"4/1\\\":\\\"0,0,0,0,0,0\\\",\\\"4/3\\\":\\\"0,0,0,0,0\\\",\\\"4/2\\\":\\\"2,0,0,0,0,0,0,0\\\",\\\"3/10\\\":\\\"0,0,0,0,0,0\\\",\\\"2/9\\\":\\\"0,0,0,0,0\\\",\\\"1/10\\\":\\\"0,0,2,0\\\",\\\"2/8\\\":\\\"2,0,0,0\\\"}\"}]"
}

Subscribe to live score changes for a particular event.

Channel name format

live_scores:{sportXEventId}

Name Type Description
sportXEventId string The event ID you wish to subscribe to

Message payload format

Name Type Description
teamOneScore number The current score for team one. Referring to teamOneName in the Market object itself
teamTwoScore number The current score for team two. Referring to teamTwoName in the Market object itself
sportXEventId string The event ID for this update
currentPeriod string An identifier for the current period
periodTime string The current time for the period. "-1" if not applicable (for example, in tennis)
sportId number The sport ID for this market
leagueId number The league ID for this market
periods Period Individual period information
extra string JSON encoded extra data for this live score update

where a Period object looks like

Name Type Description
label string The period name
isFinished boolean true if the period is over
teamOneScore string The score of team one. Referring to teamOneName in the Market object itself
teamTwoScore string The score of team two. Referring to teamTwoName in the Market object itself

To get the actual line, you'll have to fetch the market using the marketHash

Trade updates

const channel = realtime.channels.get(`recent_trades`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this

{
  "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
  "bettor": "0x814d79A9940CbC5Af4C19cAa118EC065a77CD31f",
  "stake": "9999999999999999999",
  "odds": "48442571331686290000",
  "orderHash": "0xf7321de419b8887eafe5756d25db37ed2796dfc2495a49e286f13c8533fddb67",
  "marketHash": "0x32d6c7d300dc44c795e2bdb0c735d9ad74fd2bbece890129d4a5ea0ec6b566f1",
  "maker": false,
  "betTime": 1625668455,
  "settled": false,
  "bettingOutcomeOne": true,
  "fillHash": "0x027f3237d9dc9dfa6068b60d852c3e9727768683a8c43b2e1a436029f0de924e",
  "status": "SUCCESS",
  "tradeStatus": "SUCCESS"
}

Subscribe to all trade updates on the exchange. You will receive updates when a trade is settled or a new trade is placed.

Channel name format

recent_trades

Message payload format

See the trades section for the format of the message

Consolidated Trade updates

const channel = realtime.channels.get(`recent_trades_consolidated`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this

{
  "baseToken": "0x5147891461a7C81075950f8eE6384e019e39ab98",
  "tradeStatus": "PENDING",
  "bettor": "0x1562258769E6c0527bd83502E9dfc803929fa446",
  "totalStake": "10.0",
  "weightedAverageOdds": "70750000000000000000",
  "marketHash": "0x5bea2dc8ad1be455547d1ed043cea34457c049a4f6aad0d4ddcb19107e9057f3",
  "maker": false,
  "settled": false,
  "fillHash": "0xd81d39b80f1336affc84c6f03944ad5bc6d6ee1cd7a6ba8318595812d8ad11c7",
  "gameLabel": "Andrey Rublev vs Fabian Marozsan",
  "sportXeventId": "L13351999",
  "gameTime": "2024-07-25T16:00:00.000Z",
  "leagueLabel": "ATP Umag",
  "bettingOutcomeLabel": "Andrey Rublev",
  "bettingOutcome": 1,
  "chainVersion": "SXN",
}

Subscribe to all consolidated trade updates on the exchange. You will receive updates when a consolidated trade is settled or a new consolidated trade available.

Channel name format

recent_trades_consolidated

Message payload format

See the trades section for the format of the message

Active order updates

const user = "0x082605F78dD83A8423113ecbEB794Fb3FFE470a2";
const token = process.env.USDC_TOKEN_ADDRESS; // get from https://api.sx.bet/metadata
const channel = realtime.channels.get(`active_orders:${token}:${user}`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this

[
  [
    "0x7bd766486f589f3e272d48294d8881fe68aae7704f7b2ef0a50bf6128be44271",
    "0xde53cf70e510eb5aa63a4bd6582579769980e0c1749b64caa7e6773c0c308188",
    "INACTIVE",
    "1000000000",
    "1000000000"
    "20306024864520233000",
    2209006800,
    1625691600,
    "1271418014917937117393617219009886912225128221921196717331617268846160092273",
    false,
    "0xbf099ab02255d5e2a9e063dc43a7afe96e65f5e8fc2ed3d2ba60b0a3fcadb3441bf32271293e85b7a795c9d86a2304035a0da3285113e746547e236bc58885e01c",
    "6982204685293715457",
    "SXR",
    "L13772588",
  ]
]

Subscribe to changes in a particular user's orders. You will receive updates when orders are filled, cancelled, or posted. Note that for performance reasons, updates are delayed by at most 100ms.

Channel name format

active_orders:{token}:{user}

Name Type Description
token string Restrict updates to only orders denominated in this token
user string The user to subscribe to

Message payload format

The order is packed into an array and the fields are sent in the below order, with the 0th index as the first row. Note that these are the same fields as mentioned in the the orders section, with an additional status and updateTime field.

Name Type Description
orderHash string A unique identifier for this order
marketHash string The market for this order
status string "ACTIVE" if this order is still valid, "INACTIVE" otherwise
fillAmount string How much this order has been filled in Ethereum units up to a max of totalBetSize. See the token section of how to convert this into nominal amounts
totalBetSize string The total size of this order in Ethereum units. See the the token section section for how to convert this into nominal amounts.
percentageOdds string The odds that the maker receives in the sportx protocol format. To convert to an implied odds divide by 10^20. To convert to the odds that the taker would receive if this order would be filled in implied format, use the formula takerOdds=1-percentageOdds/10^20. See the unit conversion section for more details.
expiry number Depcreated field: the time in unix seconds after which this order is no longer valid. Always 2209006800
apiExpiry number The time in unix seconds after which this order is no longer valid
salt string A random number to differentiate identical orders
isMakerBettingOutcomeOne boolean true if the maker is betting outcome one (and hence taker is betting outcome two if filled)
signature string Signature of the maker on this order
updateTime string Server-side clock time for the last modification of this order.
chainVersion string SXN or SXR
sportXeventId string The event related to this order

Note that the messages are sent in batches in an array. If you receive two updates for the same orderHash within an update, you can order them by updateTime after converting the updateTime to a BigInt or BigNumber.

Order book updates

const marketHash =
  "0x04b9af76dfb92e71500975db77b1de0bb32a0b2413f1b3facbb25278987519a7";
const token = "0xa25dA0331Cd053FD17C47c8c34BCCBAaF516C438";
const channel = realtime.channels.get(`order_book:${token}:${marketHash}`);
channel.subscribe((message) => {
  console.log(message.data);
});

The above command returns JSON structured like this

[
  [
    "0x7bd766486f589f3e272d48294d8881fe68aae7704f7b2ef0a50bf6128be44271",
    "INACTIVE",
    "1000000000",
    "0x9883D5e7dC023A441A01Ef95aF406C69926a0AB6",
    "1000000000",
    "20306024864520233000",
    2209006800,
    1625691600,
    "1271418014917937117393617219009886912225128221921196717331617268846160092273",
    false,
    "0xbf099ab02255d5e2a9e063dc43a7afe96e65f5e8fc2ed3d2ba60b0a3fcadb3441bf32271293e85b7a795c9d86a2304035a0da3285113e746547e236bc58885e01c",
    "6982204685293715457",
    "SXR",
    "L13772588",
  ]
]

Subscribe to changes in a particular order book. You will receive updates when orders are filled, cancelled, or posted. Note that for performance reasons, updates are delayed by at most 100ms. Updates are packed into arrays to reduce total bandwidth.

Channel name format

order_book:{token}:{marketHash}

Name Type Description
token string Restrict updates to only orders denominated in this token
marketHash string The market to subscribe to

Message payload format

The order is packed into an array and the fields are sent in the below order, with the 0th index as the first row. Note that these are the same fields as mentioned in the the orders section, with an additional status and updateTime field.

Name Type Description
orderHash string A unique identifier for this order
status string "ACTIVE" if this order is still valid, "INACTIVE" otherwise
fillAmount string How much this order has been filled in Ethereum units up to a max of totalBetSize. See the token section of how to convert this into nominal amounts
maker string The market maker for this order
totalBetSize string The total size of this order in Ethereum units. See the the token section section for how to convert this into nominal amounts.
percentageOdds string The odds that the maker receives in the sportx protocol format. To convert to an implied odds divide by 10^20. To convert to the odds that the taker would receive if this order would be filled in implied format, use the formula takerOdds=1-percentageOdds/10^20. See the unit conversion section for more details.
expiry number Depcreated field: the time in unix seconds after which this order is no longer valid. Always 2209006800
apiExpiry number The time in unix seconds after which this order is no longer valid
salt string A random number to differentiate identical orders
isMakerBettingOutcomeOne boolean true if the maker is betting outcome one (and hence taker is betting outcome two if filled)
signature string Signature of the maker on this order
updateTime string Server-side clock time for the last modification of this order.
chainVersion string SXN or SXR
sportXeventId string The event related to this order

Note that the messages are sent in batches in an array. If you receive two updates for the same orderHash within an update, you can order them by updateTime after converting the updateTime to a BigInt or BigNumber.

Best Practices

const Ably = require("ably");
import axios from "axios";

async function getOrders(marketHash, token) {
  const response = await axios.get(
    `https://api.sx.bet/orders?marketHashes=${marketHash}&baseToken=${token}`,
    {
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": this.apiKey,
      },
    }
  );
  console.log(response.data);
}

async function orderStream(realtime, marketHash, token) {
  const channel = realtime.channels.get(`order_book:${token}:${marketHash}`, {
    params: { rewind: "10s" },
  });
  channel.subscribe((message) => {
    console.log(message.data);
  });
}

async function createTokenRequest() {
  console.log("createTokenRequest");
  const response = await axios.get("https://api.sx.bet/user/token", {
    headers: {
      "X-Api-Key": this.apiKey,
      "Content-Type": "application/json",
    },
  });
  return response.data;
}

async function initialize() {
  const ablyClient = new Ably.Realtime.Promise({
    authUrl: "https://ably.com/ably-auth/token/docs",
  });
  const realtime = new Ably.Realtime.Promise({
    authCallback: async (tokenParams, callback) => {
      try {
        const tokenRequest = await createTokenRequest();
        callback(null, tokenRequest);
      } catch (error) {
        callback(error, null);
      }
    },
  });
  await ablyClient.connection.once("connected");
  return realtime;
}

async function main() {
  const realtime = await initialize();
  await getOrders(this.marketHash, this.token);
  await orderStream(realtime, this.marketHash, this.token);
}

main();

For optimal state updates, we recommend a combination of HTTP requests and channel subscriptions, utilizing the rewind parameter. HTTP requests provide the current state, while channel subscriptions keep the state updated. The rewind parameter ensures playback of past events, preventing any missed events between the HTTP call and subscription. See this link for an overview of the rewind parameter and more.

API Key

This key is currently required to access the WebSocket through Ably, for realtime updates. Otherwise, in order to use the SX.Bet API, you do NOT need an API Key. The standard API user can perform all the requests provided in this document. There is a baseline rate limiter applied to all requests, regardless of API key.

The API Key will

Generating API Key

  1. Visit sx.bet and register/login to your account. You can connect your MetaMask wallet, or login using your Fortmatic email address.
  2. If using MetaMask, sign the Signature Request.
  3. Click the Account tab on the top navigation bar.
  4. Click the Overview tab on the account navigation bar.
  5. You will see an API Credentials card. Complete the Enhanced Verification if you have not yet done so by clicking the link in the card.
  6. If you've signed in and completed the enhanced verification, you can now click the button: GENERATE API KEY NOW. An API Key will be displayed.
  7. The API Key generated will not be displayed again, so please copy and save this key for future use.

Usage

curl --location --request GET 'https://api.sx.bet/user/token' \
--header 'X-Api-Key: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'

You must fill your API Key in the header X-Api-Key. The above is a sample command to create an Ably Token Request.

Once your API Key is generated (see above), you must add it as a HTTP Header with the name: X-Api-Key.

Heartbeat

A user may register a heartbeat to SX Bet API using the Heartbeat service. An API Key is necessary for this service. Once you register a heartbeat with the desired timeout value, all your open orders will be automatically cancelled if another heartbeat request is not sent within that timeout period.

This will ensure that when loss of connectivity occurs between your service and SX Bet API, there will be no exposure left on the orderbook.

Register heartbeat

To register and refresh your heartbeat with 0 > timeoutSeconds <= 3600. Ping this endpoint to maintain your heartbeat after initial registration.

curl --location --request POST 'https://api.sx.bet/heartbeat' \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <YOUR-API-KEY>' \
--data-raw '{
    "requestor": "<YOUR-ACCOUNT-ADDRESS>",
    "timeoutSeconds": 10
}'

The above command returns JSON structured like this

{
    "status": "success",
    "data": {
        "requestor": "<YOUR-ACCOUNT-ADDRESS>",
        "timeoutSeconds": 10,
        "expiresAt": "2024-11-12T14:35:06.614Z"
    }
}

Cancel heartbeat

To cancel a registered heartbeat, you may use the same request to register a heartbeat but use timeoutSeconds=0. This will deactivate the heartbeat and orders will NOT be cancelled automatically.

curl --location --request POST 'https://api.sx.bet/heartbeat' \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <YOUR-API-KEY>' \
--data-raw '{
    "requestor": "<YOUR-ACCOUNT-ADDRESS>",
    "timeoutSeconds": 0
}'

The above command returns JSON structured like this

{
    "status": "success",
    "data": {
        "requestor": "<YOUR-ACCOUNT-ADDRESS>",
        "timeoutSeconds": 0,
        "expiresAt": "2024-11-12T14:35:06.614Z"
    }
}

HTTP Request

POST https://api.sx.bet/heartbeat

Request payload parameters

Name Required Type Description
requestor true string The ethereum address associated with your account
timeoutSeconds true number The number of seconds before heartbeat service times out and cancels your open orders

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> requestor string The ethereum address associated with your account
> timeoutSeconds number The number of seconds before heartbeat service times out and cancels your open orders
> expiresAt Date A JS Date object describing when the heartbeat will timeout/expire (UTC)

Leagues

Get leagues

curl --location --request GET 'https://api.sx.bet/leagues'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "leagueId": 20,
      "label": "The Memorial Tournament_Round 1",
      "sportId": 4,
      "active": false
    },
    {
      "leagueId": 5,
      "label": "The Masters_Round 1",
      "sportId": 4,
      "active": false
    },
    {
      "leagueId": 1,
      "label": "NBA",
      "sportId": 1,
      "active": true
    },
    {
      "leagueId": 28,
      "label": "US Open_Round 4",
      "sportId": 4,
      "active": false
    },
    {
      "leagueId": 10,
      "label": "Wells Fargo Championship_Round 1",
      "sportId": 4,
      "active": false
    },
    {
      "leagueId": 37,
      "label": "RBC Heritage_Round 4",
      "sportId": 4,
      "active": false
    },
    {
      "leagueId": 3,
      "label": "NHL",
      "sportId": 2,
      "active": true
    },
    {
      "leagueId": 34,
      "label": "UFC",
      "sportId": 7,
      "active": true
    }
  ]
}

This endpoint returns all the leagues supported by SX.bet

HTTP Request

GET https://api.sx.bet/leagues

Query parameters

Name Required Type Description
sportId false number Only return leagues for this particular sport ID.

Response format

Name Type Description
status string success or failure if the request succeeded or not
data League[] An array of league objects

A League object looks like this

Name Type Description
leagueID number The ID for this league
label string The name of this league
sportID number The ID of the sport this league corresponds to
active boolean Whether or not the league is active on SX.bet currently

Get active leagues

curl --location --request GET 'https://api.sx.bet/leagues/active'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "leagueId": 34,
      "label": "UFC",
      "sportId": 7,
,
      "eventsByType": {
        "game-lines": 36
      }
    },
    {
      "leagueId": 30,
      "label": "Champions League_UEFA",
      "sportId": 5,
,
      "eventsByType": {
        "outright-winner": 32,
        "game-lines": 10
      }
    },
    {
      "leagueId": 29,
      "label": "English Premier League",
      "sportId": 5,
,
      "eventsByType": {
        "outright-winner": 23,
        "game-lines": 10
      }
    },
    {
      "leagueId": 1197,
      "label": "UEFA Nations League",
      "sportId": 5,
,
      "eventsByType": {
        "outright-winner": 19,
        "game-lines": 2
      }
    },
    {
      "leagueId": 244,
      "label": "Bundesliga",
      "sportId": 5,
,
      "eventsByType": {
        "game-lines": 9,
        "outright-winner": 18
      }
    },
    {
      "leagueId": 1114,
      "label": "La Liga",
      "sportId": 5,
,
      "eventsByType": {
        "game-lines": 10,
        "outright-winner": 37
      }
    },
    {
      "leagueId": 1112,
      "label": "Ligue 1",
      "sportId": 5,
,
      "eventsByType": {
        "game-lines": 20,
        "outright-winner": 20
      }
    },
    {
      "leagueId": 1113,
      "label": "Serie A",
      "sportId": 5,
,
      "eventsByType": {
        "outright-winner": 20,
        "game-lines": 10
      }
    },
    {
      "leagueId": 1236,
      "label": "Portugal Primeira Liga",
      "sportId": 5,
,
      "eventsByType": {
        "outright-winner": 17,
        "game-lines": 9
      }
    }
  ]
}

This endpoint returns all the currently active leagues with markets in them.

HTTP Request

GET https://api.sx.bet/leagues/active

Query parameters

Name Required Type Description
sportId true number Only return active leagues under this sport
chainVersion false string Must be either SXN or SXR.
If not passed, data from both chains are returned. See migration docs

Response format

See get leagues section for how the response object is formatted. There is an additional field eventsByType which maps the number of unique events within a particular bet group (for example, game-lines or outright-winner).

Get league teams

curl --location --request GET 'https://api.sx.bet/leagues/teams/1'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": {
    "teams": [
      {
        "id": 725,
        "name": "Juventus"
      },
      {
        "id": 724,
        "name": "Inter"
      },
      {
        "id": 1058,
        "name": "Napoli"
      },
      {
        "id": 719,
        "name": "Atalanta"
      },
      {
        "id": 726,
        "name": "Lazio"
      },
      {
        "id": 728,
        "name": "AC Milan"
      },
      {
        "id": 732,
        "name": "AS Roma"
      },
      {
        "id": 723,
        "name": "Fiorentina"
      },
      {
        "id": 134312,
        "name": "Sassuolo"
      },
      {
        "id": 720,
        "name": "Bologna"
      },
      {
        "id": 733,
        "name": "Torino"
      },
      {
        "id": 1049,
        "name": "Cagliari"
      },
      {
        "id": 1062,
        "name": "Sampdoria"
      },
      {
        "id": 736,
        "name": "Hellas Verona"
      },
      {
        "id": 729,
        "name": "Parma"
      },
      {
        "id": 1057,
        "name": "Genoa"
      },
      {
        "id": 734,
        "name": "Udinese"
      },
      {
        "id": 1052,
        "name": "Crotone"
      },
      {
        "id": 115743,
        "name": "Spezia"
      },
      {
        "id": 134313,
        "name": "Benevento"
      }
    ]
  }
}

This endpoint returns all the teams under a particular league

HTTP Request

GET https://api.sx.bet/leagues/teams/:id

Request parameters

Name Required Type Description
id true number Return the teams for this league ID

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
teams Team[] An array of teams

A Team object looks like this

Name Type Description
id number The ID for this team
name string The name of this team

Sports

Get sports

curl --location --request GET 'https://api.sx.bet/sports'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "sportId": 1,
      "label": "Basketball"
    },
    {
      "sportId": 2,
      "label": "Hockey"
    },
    {
      "sportId": 3,
      "label": "Baseball"
    },
    {
      "sportId": 4,
      "label": "Golf"
    },
    {
      "sportId": 5,
      "label": "Soccer"
    },
    {
      "sportId": 6,
      "label": "Tennis"
    },
    {
      "sportId": 7,
      "label": "Mixed Martial Arts"
    },
    {
      "sportId": 8,
      "label": "Football"
    },
    {
      "sportId": 9,
      "label": "E Sports"
    },
    {
      "sportId": 10,
      "label": "Custom"
    },
    {
      "sportId": 11,
      "label": "Rugby Union"
    },
    {
      "sportId": 12,
      "label": "Racing"
    },
    {
      "sportId": 13,
      "label": "Boxing"
    },
    {
      "sportId": 14,
      "label": "Crypto"
    },
    {
      "sportId": 15,
      "label": "Cricket"
    },
    {
      "sportId": 16,
      "label": "Economics"
    },
    {
      "sportId": 17,
      "label": "Politics"
    },
    {
      "sportId": 18,
      "label": "Entertainment"
    },
    {
      "sportId": 19,
      "label": "Medicinal"
    },
    {
      "sportId": 20,
      "label": "Rugby League"
    }
  ]
}

This endpoint retrieves all sports available on the exchange

HTTP Request

GET https://api.sx.bet/sports

Response format

Name Type Description
status string success or failure if the request succeeded or not
data Sport[] Sports available on the exchange

A Sport object looks like

Name Type Description
sportId number The ID of the sport
label string The sport name

Fixtures

Get fixtures

curl --location --request GET 'https://api.sx.bet/fixture/active?leagueId=2'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "participantOneName": "Nevada Wolf Pack",
      "participantTwoName": "North Dakota State",
      "startDate": "2020-11-25T20:00:00.000Z",
      "status": 1,
      "leagueId": 2,
      "leagueLabel": "NCAA",
      "sportId": 1,
      "eventId": "L6206070"
    },
    {
      "participantOneName": "UMass Lowell River Hawks",
      "participantTwoName": "San Francisco Dons",
      "startDate": "2020-11-25T20:00:00.000Z",
      "status": 1,
      "leagueId": 2,
      "leagueLabel": "NCAA",
      "sportId": 1,
      "eventId": "L6208648"
    },
    {
      "participantOneName": "William Jewell",
      "participantTwoName": "Indianapolis",
      "startDate": "2020-11-28T03:45:00.000Z",
      "status": 1,
      "leagueId": 2,
      "leagueLabel": "NCAA",
      "sportId": 1,
      "eventId": "L6217784"
    }
  ]
}

This endpoint returns current active fixtures for a particular league. A fixture can also be thought of as an event and multiple markets are under a particular event. Note that this endpoint only returns fixtures that have a status of either 1, 2, 6, 7, 8, or 9. See the status table in this section for more details.

HTTP Request

GET https://api.sx.bet/fixture/active

Query parameters

Name Required Type Description
leagueId true number The ID of the league

Response format

Name Type Description
status string success or failure if the request succeeded or not
data Fixture[] The active fixtures for this particular league

A Fixture object looks like this

Name Type Description
participantOneName string? The first participant in the fixture. Present if it's a two-participant event.
participantTwoName string? The second participant in the fixture. Present if it's a two-participant event.
participants string[]? All the participants in the fixture. Present if it's an n-participant event.
startDate string The start date of the event in UTC time
status number The status of the fixture. See the status table in this section for more details.
leagueId number The ID of the league this fixture belongs to
leagueLabel string The name of the league this fixture belongs to
sportId number The ID of the sport this fixture belongs to
eventId string The ID of this fixture

Get fixture status

curl --location --request GET 'https://api.sx.bet/fixture/status'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": {
    "L6217784": 1
  }
}

This endpoint returns the status of the passed event IDs.

HTTP Request

GET https://api.sx.bet/fixture/status

Query parameters

Name Required Type Description
sportXEventIds true string[] An array of event IDs (comma separated)

Response format

Name Type Description
status string success or failure if the request succeeded or not
data obj Mapping from event Id to a status flag for each event's status.

The possible statuses for a fixture are the following

ID Name Description
1 Not started yet The event has not started yet
2 In progress The event is live
3 Finished The event is finished
4 Cancelled The event has been cancelled
5 Postponed The event has been postponed. Postponed is sent for events which are postponed and will be played at a later time. In case no new start date/time is available within 48 hours, the event will be cancelled. In case a new start time is available within 48 hours, we will update it to "Not started yet" with the new start time.
6 Interrupted The event has been interrupted. Interrupted is sent for interrupted events (for example - rain delay). We will continue the coverage once the event is renewed, under the same event ID.
7 Abandoned The event has been abandoned. Abandoned is a final status sent for abandoned events (player injury in Tennis for example), these matches will not resume.
8 Coverage lost The coverage for this event has been lost
9 About to start The event has not started but is about to. Note: this status will be shown up to 30 minutes before the event has started.

Live scores

Get live scores

curl --location --request GET 'https://api.sx.bet/live-scores'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "createdAt": "2021-07-08T17:57:38.057Z",
      "currentPeriod": "8th Inning",
      "extra": "[{\"Name\":\"Strikes\",\"Value\":\"0\"},{\"Name\":\"Turn\",\"Value\":\"2\"},{\"Name\":\"Balls\",\"Value\":\"1\"},{\"Name\":\"Outs\",\"Value\":\"2\"},{\"Name\":\"Bases\",\"Value\":\"0/0/0\"}]",
      "leagueId": 171,
      "periodTime": "-1",
      "periods": [
        {
          "label": "1st Inning",
          "isFinished": true,
          "teamOneScore": "0",
          "teamTwoScore": "2"
        },
        {
          "label": "2nd Inning",
          "isFinished": true,
          "teamOneScore": "0",
          "teamTwoScore": "0"
        },
        {
          "label": "3rd Inning",
          "isFinished": true,
          "teamOneScore": "0",
          "teamTwoScore": "0"
        },
        {
          "label": "4th Inning",
          "isFinished": true,
          "teamOneScore": "0",
          "teamTwoScore": "0"
        },
        {
          "label": "5th Inning",
          "isFinished": true,
          "teamOneScore": "0",
          "teamTwoScore": "0"
        },
        {
          "label": "6th Inning",
          "isFinished": true,
          "teamOneScore": "0",
          "teamTwoScore": "0"
        },
        {
          "label": "7th Inning",
          "isFinished": true,
          "teamOneScore": "1",
          "teamTwoScore": "0"
        },
        {
          "label": "8th Inning",
          "isFinished": false,
          "teamOneScore": "0",
          "teamTwoScore": "0"
        }
      ],
      "sportId": 3,
      "teamOneScore": 1,
      "teamTwoScore": 2,
      "updatedAt": "2021-07-08T20:35:21.607Z",
      "sportXEventId": "L7187811"
    }
  ]
}

This endpoint retrieves live scores for a particular event ID.

HTTP Request

GET https://api.sx.bet/live-scores

Query parameters

Name Required Type Description
sportXEventIds true string[] An array of event IDs

Response format

Name Type Description
status string success or failure if the request succeeded or not
data Score[] The resulting scores for the fixtures passed in

A Score object has the following format

Name Type Description
currentPeriod string The current period label
extra string Extra data for this match
leagueId number The league ID for this match
periodTime number The time in the period. If it's -1, it is not available or the game is finished
periods Period[] All of the periods in the match
sportId number The ID of the sport for this match
teamOneScore number The current score for team one
teamTwoScore number The current score for team two
sportXEventId string The event ID for this match

A Period object has the following format

Name Type Description
label string The current period name
isFinished boolean true if the period is over
teamOneScore string The score of team one in this period
teamTwoScore string The score of team two in this period

Markets

Get active markets

curl --location --request GET 'https://api.sx.bet/markets/active?onlyMainLine=true'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": {
    "markets": [
      {
        "status": "ACTIVE",
        "marketHash": "0x0d64c52e8781acdada86920a2d1e5acd6f29dcfe285cf9cae367b671dff05f7d",
        "outcomeOneName": "Nikoloz Basilashvili",
        "outcomeTwoName": "Carlos Alcaraz",
        "outcomeVoidName": "NO_CONTEST",
        "teamOneName": "Nikoloz Basilashvili",
        "teamTwoName": "Carlos Alcaraz",
        "type": 226,
        "gameTime": 1622735700,
        "sportXEventId": "L7032829",
        "liveEnabled": true,
        "sportLabel": "Tennis",
        "sportId": 6,
        "leagueId": 1070,
  ,
        "leagueLabel": "ATP French Open",
        "group1": "ATP French Open"
      },
      {
        "status": "ACTIVE",
        "marketHash": "0xe609a49d083cd41214a0db276c1ba323c4a947eefd2e4260386fec7b5d258188",
        "outcomeOneName": "Over 36.5",
        "outcomeTwoName": "Under 36.5",
        "outcomeVoidName": "NO_GAME_OR_EVEN",
        "teamOneName": "Nikoloz Basilashvili",
        "teamTwoName": "Carlos Alcaraz",
        "type": 2,
        "gameTime": 1622735700,
        "line": 36.5,
        "sportXEventId": "L7032829",
        "liveEnabled": true,
        "sportLabel": "Tennis",
        "sportId": 6,
        "leagueId": 1070,
  ,
        "leagueLabel": "ATP French Open",
        "mainLine": true,
        "group1": "ATP French Open"
      },
      {
        "status": "ACTIVE",
        "marketHash": "0x85e588d72b4a2ec6386846a6f4706dba2124410e38bd8e8f7f37dee9728e0d84",
        "outcomeOneName": "Nikoloz Basilashvili +1.5",
        "outcomeTwoName": "Carlos Alcaraz -1.5",
        "outcomeVoidName": "NO_GAME_OR_EVEN",
        "teamOneName": "Nikoloz Basilashvili",
        "teamTwoName": "Carlos Alcaraz",
        "type": 3,
        "gameTime": 1622735700,
        "line": 1.5,
        "sportXEventId": "L7032829",
        "liveEnabled": true,
        "sportLabel": "Tennis",
        "sportId": 6,
        "leagueId": 1070,
  ,
        "leagueLabel": "ATP French Open",
        "mainLine": true,
        "group1": "ATP French Open"
      }
    ],
    "nextKey": "60c7b8f54da0ad001aa3261c"
  }
}

This endpoint retrieves active markets on the exchange. It does not return markets that have been settled or reported. Note that to retrieve odds for a particular market, you must query the orders endpoint the orders endpoint separately.

HTTP Request

GET https://api.sx.bet/markets/active

Query parameters

Name Required Type Description
onlyMainLine false boolean If set to true, the result will only include main lines on spread and over under markets
eventId false string If set, it will only include markets for a particular sportXEventId
leagueId false number If set, it will only include markets for a particular league ID
sportIds false number[] If set, it will only include markets for particular sport IDs (comma separated)
liveOnly false boolean If set, it will only include markets that are currently available for in-play betting
betGroup false string If set, it will only include markets for a particular bet group
type false number[] If set, it will only include markets for those particular market types. See below for the options
paginationKey false string Used for pagination. Pass the nextKey returned from the previous request to retrieve the next set of records.
pageSize false number Used for pagination. Requested page size. Each call will only return up to this amount of records. Maximum of 50
chainVersion false string Must be either SXN or SXR.
If not passed, data from both chains are returned. See migration docs

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> markets Market[] The active markets
> nextKey string Use this key as the paginationKey to retrieve the next set of records, if any

A market object looks like this

Name Type Description
status string ACTIVE or INACTIVE
marketHash string The unique identifier for the market
outcomeOneName string Outcome one for this market
outcomeTwoName string Outcome two for this market
outcomeVoidName string Outcome void for this market
teamOneName string The name of the first team/player participating
teamTwoName string The name of the second team/player participating
type MarketType The type of the market
gameTime number The UNIX timestamp of the game
line number? The line of the market. Only applicable to markets with a line
sportXEventId string The unique event ID for this market
liveEnabled boolean Whether or not this match is available for live betting
sportLabel string The name of the sport for this market
sportId number The ID of the sport for this market
leagueId number The league ID for this market
leagueLabel string The name of the league for this market
mainLine boolean? If this market is currently the main line or not. If the market is not a market with multiple lines, this field will not be present
group1 string Indicator to the client of how to display this market
group2 string? Indicator to the client of how to display this market
teamOneMeta string? Extra metadata for team one
teamTwoMeta string? Extra metadata for team two
marketMeta string? Extra metadata for the market overall
legs Market[]? If this is a Parlay Market, this field will contain an array of the underlying Legs as a Market object
chainVersion string? SXN or SXR. See migration docs.

A MarketType can currently be one of the following

ID Name Has Lines Description Bet Group
1 1X2 false Who will win the game (1X2) 1X2
52 12 false Who will win the game game-lines
88 To Qualify false Which team will qualify game-lines
226 12 Including Overtime false Who will win the game including overtime (no draw) game-lines
3 Asian Handicap true Who will win the game with handicap (no draw) game-lines
201 Asian Handicap Games true Who will win more games with handicap (no draw) game-lines
342 Asian Handicap Including Overtime true Who will win the game with handicap (no draw) including Overtime game-lines
2 Under/Over true Will the score be under/over a specific line game-lines
835 Asian Under/Over true Will the score be under/over specific asian line game-lines
28 Under/Over Including Overtime true Will the score including overtime be over/under a specific line game-lines
29 Under/Over Rounds true Will the number of rounds in the match will be under/over a specific line game-lines
166 Under/Over Games true Number of games will be under/over a specific line game-lines
1536 Under/Over Maps true Will the number of maps be under/over a specific line game-lines
274 Outright Winner false Winner of a tournament, not a single match outright-winner
202 First Period Winner false Who will win the 1st Period Home/Away first-period-lines
203 Second Period Winner false Who will win the 2nd Period Home/Away second-period-lines
204 Third Period Winner false Who will win the 3rd Period Home/Away third-period-lines
205 Fourth Period Winner false Who will win the 4th Period Home/Away fourth-period-lines
866 Set Spread true Which team/player will win more sets with handicap set-betting
165 Set Total true Number of sets will be under/over a specific line set-betting
53 Asian Handicap Halftime true Who will win the 1st half with handicap (no draw) first-half-lines
64 Asian Handicap First Period true Who will win the 1st period with handicap (no draw) first-period-lines
65 Asian Handicap Second Period true Who will win the 2nd period with handicap (no draw) second-period-lines
66 Asian Handicap Third Period true Who will win the 3rd period with handicap (no draw) third-period-lines
63 12 Halftime false Who will win the 1st half (no draw) first-half-lines
77 Under/Over Halftime true Will the score in the 1st half be under/over a specific line first-half-lines
21 Under/Over First Period true Will the score in the 1st period be under/over a specific line first-period-lines
45 Under/Over Second Period true Will the score in the 2nd period be under/over a specific line second-period-lines
46 Under/Over Third Period true Will the score in the 3rd period be under/over a specific line third-period-lines
281 1st Five Innings Asian handicap true Who will win the 1st five innings with handicap (no draw) first-five-innings
1618 1st 5 Innings Winner-12 false Who will win in the 1st five innings first-five-innings
236 1st 5 Innings Under/Over true Will the score in the 1st five innings be under/over a specific line first-five-innings

More types will be added continuously.

Get specific markets

curl --location --request GET 'https://api.sx.bet/markets/find'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "status": "ACTIVE",
      "marketHash": "0x3cba25f2253035b015b9bb555c1bf900f6737704d57425dd2a5b60e929c33b81",
      "outcomeOneName": "Over 2.5",
      "outcomeTwoName": "Under 2.5",
      "outcomeVoidName": "NO_GAME_OR_EVEN",
      "teamOneName": "Aston Villa",
      "teamTwoName": "Burnley",
      "type": 2,
      "gameTime": 1608228000,
      "line": 2.5,
      "reportedDate": 1608234719,
      "outcome": 2,
      "teamOneScore": 0,
      "teamTwoScore": 0,
      "sportXEventId": "L6247212",
      "liveEnabled": false,
      "sportLabel": "Soccer",
      "sportId": 5,
      "leagueId": 29,
,
      "leagueLabel": "English Premier League",
      "group1": "English Premier League"
    }
  ]
}

This endpoint retrieves specific markets

HTTP Request

GET https://api.sx.bet/markets/find

Query parameters

Name Required Type Description
marketHashes true string[] The market hashes of the markets to retrieve. Comma separated. Maximum 30.

Response format

Name Type Description
status string success or failure if the request succeeded or not
data Market[] The response data

See active markets section for how the Market object is formatted. Note that there are a few additional fields if you are querying a market that has been settled/reported:

Name Type Description
reportedDate number Time in unix seconds of when the market was reported
outcome number The outcome of the market. Can be one of 0 1 or 2. 0 means the market was voided and stakes were returned to bettors. 1 means the outcome labeled outcomeOneName was the outcome. 2 means the outcome labeled outcomeTwoName was the outcome.
teamOneScore number Final score of team one
teamTwoScore number Final score of team two

Error Responses

Error Code Description
BAD_MARKET_HASHES Invalid marketHashes or more than 30 marketHashes queried
curl --location --request GET 'https://api.sx.bet/markets/popular'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": [
    {
      "status": "ACTIVE",
      "marketHash": "0x66fc26c008c724d5dec2fb0bf8fb8797ecc49a4302a785cc8e5e7faf96249d8a",
      "outcomeOneName": "Ismagulov D.",
      "outcomeTwoName": "Rafael da Silva Alves",
      "outcomeVoidName": "NO_GAME",
      "teamOneName": "Ismagulov D.",
      "teamTwoName": "Rafael da Silva Alves",
      "type": 226,
      "gameTime": 1621724400,
      "sportXEventId": "L6896568",
      "liveEnabled": false,
      "sportLabel": "Mixed Martial Arts",
      "sportId": 7,
      "leagueId": 34,
,
      "leagueLabel": "UFC",
      "group1": "UFC"
    },
    {
      "status": "ACTIVE",
      "marketHash": "0xad3494f1bf10e826cd8e0faecd42e0f578f2bc9de748946fbe3d833a11764b89",
      "outcomeOneName": "West Ham United -0.5",
      "outcomeTwoName": "Southampton +0.5",
      "outcomeVoidName": "NO_GAME_OR_EVEN",
      "teamOneName": "West Ham United",
      "teamTwoName": "Southampton",
      "type": 3,
      "gameTime": 1621782000,
      "line": -0.5,
      "sportXEventId": "L6973172",
      "liveEnabled": false,
      "sportLabel": "Soccer",
      "sportId": 5,
      "leagueId": 29,
,
      "leagueLabel": "English Premier League",
      "mainLine": true,
      "group1": "English Premier League"
    }
  ]
}

This endpoint retrieves the top 10 popular markets by volume.

HTTP Request

GET https://api.sx.bet/markets/popular

Query parameters

Name Required Type Description
chainVersion false string Must be either SXN or SXR.
If not passed, data from both chains are returned. See migration docs

Response format

Name Type Description
status string success or failure if the request succeeded or not
data Market[] The response data

See active markets section for how the Market object is formatted

Parlay Markets

Bettors can request a custom parlay on SX.Bet by selecting multiple markets. When they submit a parlay request, a message is sent via Websocket (See this link for more details on the request). Once this message is sent, the web client will wait one second to allow market makers to submit orders for the parlay market.

Market makers can use the payload data from the Parlay Request to submit an order. Market makers have a three second window to post orders. After this point, bettors will be shown all available orders at the same time and no other orders will be viewable by the bettor.

Bettors can choose which order to take and will be able to fill orders like any other non-parlay order.

Market makers can cancel orders like any other non-parlay order.

Trades

Get active trades

curl --location --request GET 'https://api.sx.bet/trades'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": {
    "trades": [
      {
        "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "bettor": "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
        "stake": "10236134166947574099",
        "odds": "50583446830801460000",
        "orderHash": "0xb47329db2a3f612748094f415f9bf478cbed2f196548ec68154bd1fc543a6f09",
        "marketHash": "0x3fd03af14bf11264f5274ed4c8cc283e4479d29d33e17409e8e7d9b26ca9f030",
        "maker": true,
        "betTime": 1607708054,
        "settled": true,
        "settleValue": 1,
        "bettingOutcomeOne": false,
        "fillHash": "0xbe846c92bec584c4d2215df76ac7d53ebab25f81a30cca5811fb93f35e8b5321",
        "tradeStatus": "SUCCESS",
        "valid": true,
        "outcome": 1,
        "settleDate": "2020-12-11T20:17:45.990Z"
      },
      {
        "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "bettor": "0x683bcf3ecc5A6e2E99ff83f3300515a584108391",
        "stake": "9999999999999999999",
        "odds": "49416553169198540000",
        "orderHash": "0xb47329db2a3f612748094f415f9bf478cbed2f196548ec68154bd1fc543a6f09",
        "marketHash": "0x3fd03af14bf11264f5274ed4c8cc283e4479d29d33e17409e8e7d9b26ca9f030",
        "maker": false,
        "betTime": 1607708054,
        "settled": true,
        "settleValue": 1,
        "bettingOutcomeOne": true,
        "fillHash": "0xbe846c92bec584c4d2215df76ac7d53ebab25f81a30cca5811fb93f35e8b5321",
        "tradeStatus": "SUCCESS",
        "valid": true,
        "outcome": 1,
        "settleDate": "2020-12-11T20:17:45.990Z"
      },
      {
        "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "bettor": "0xE965292B97CD666e85FaB99e2732b1A71046cf3F",
        "stake": "17592800276256254757",
        "odds": "69184587813620070000",
        "orderHash": "0x1534133364d8b18d803a9419914bb89a651de5e9fa1845868d6844a6670c4762",
        "marketHash": "0xcb8285aeef17d824b76cf4a00ba5f2bf256048114937c85609897e7b8967e9ca",
        "maker": true,
        "betTime": 1607719092,
        "settled": true,
        "settleValue": 1,
        "bettingOutcomeOne": true,
        "fillHash": "0xc0240cf27c111d843bc4cf2de0521ab097223de933125857343e7a6fba469172",
        "tradeStatus": "SUCCESS",
        "valid": true,
        "outcome": 1,
        "settleDate": "2020-12-11T22:02:19.484Z"
      },
      {
        "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "bettor": "0x5aC843EecBf67669d4003aa49aE5e0136dc73365",
        "stake": "7835984995472770999",
        "odds": "30815412186379930000",
        "orderHash": "0x1534133364d8b18d803a9419914bb89a651de5e9fa1845868d6844a6670c4762",
        "marketHash": "0xcb8285aeef17d824b76cf4a00ba5f2bf256048114937c85609897e7b8967e9ca",
        "maker": false,
        "betTime": 1607719092,
        "settled": true,
        "settleValue": 1,
        "bettingOutcomeOne": false,
        "fillHash": "0xc0240cf27c111d843bc4cf2de0521ab097223de933125857343e7a6fba469172",
        "tradeStatus": "SUCCESS",
        "valid": true,
        "outcome": 1,
        "settleDate": "2020-12-11T22:02:19.484Z"
      }
    ],
    "nextKey": "60e4b70dc476a37a5b1b15ae",
    "pageSize": 4
  }
}

This endpoint retrieves past trades on the exchange split up by order. This is a paginated endpoint. For example, if a trade fills more than one order at once, it will show up as two entries for the bettor.

HTTP Request

GET https://api.sx.bet/trades

Query parameters

Name Required Type Description
startDate false number Only get trades placed after this time in UNIX seconds
endDate false number Only get trades placed before this time in UNIX seconds
bettor false string Only get trades placed by this bettor (regardless if maker or taker)
settled false boolean If true, only get settled trades
marketHashes false string[] Only get trades for particular markets. Comma separated
baseToken false string Only get trades placed for a particular token
maker false boolean If true, only get trades where the bettor is the maker
affiliate false string Only get trades under this affiliate
pageSize false number Requested page size. Each call will only return up to this amount of records. Default is 100.
paginationKey false string Used for pagination. Pass the nextKey returned from the previous request to retrieve the next set of records.
tradeStatus false string Filter trades to see only those with SUCCESS or FAILED status'
chainVersion false string Must be either SXN or SXR.
If not passed, data from both chains are returned. See migration docs

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> trades Trade[] The trades for the request
> nextKey string Use this key as the paginationKey to retrieve the next set of records, if any
> pageSize number Maximum amount of records on this page. Will be equal to the pageSize passed in

A Trade object has the following format

Name Type Description
baseToken string The token in which this trade was placed
bettor string The address of the bettor who placed the trade
stake string Exact token amount that was staked for the bet. To convert into a readable token amount, see the token conversion section
odds string Implied odds that the bettor received for this bet. Divide by 10^20 to get the odds in decimal format.
orderHash string The unique identifier of the order that was filled for this trade
marketHash string The unique identifier of the market for which this trade was placed
maker boolean true if the bettor is market maker in this trade
betTime number The time in UNIX seconds when the trade was placed
settled boolean true if this bet is settled (this refers to if the bet was won lost or voided, not if the trade succeeded or not)
bettingOutcomeOne boolean true if the bettor is betting outcome one in the market
fillHash string The unique identifier for this trade
tradeStatus string SUCCESS or FAILED depending on if this trade succeeded or not
valid boolean true if the trade counts toward competitions or tournaments
outcome number with settled=true, this will be 0, 1, or 2 depending on the final outcome of the market
settleDate string ISO formatted date string of when the trade was settled
chainVersion string SXN or SXR. See migration docs.
sportXeventId string The event related to this trade

Get consolidated trades

curl --location --request GET 'https://api.sx.bet/trades/consolidated'

The above command returns JSON structured like this:

{
  "status": "success",
  "data": {
    "trades": [
      {
        "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "tradeStatus": "SUCCESS",
        "bettor": "0x025b9FD9F1ed818d688e19c5F43c500bf44cEA47",
        "totalStake": "4.999999999999999999",
        "weightedAverageOdds": "67564738292011016000",
        "marketHash": "0x7b27d766b6e36b87e2b7924f792c4f84d9b459318735d69cbb7d695b0857f8d5",
        "maker": false,
        "settled": false,
        "fillHash": "0xc44e72f94e70ede9742c6b8aeb77e743e0cbf753ff3eae8bd4f1ab98b9260f0e",
        "gameLabel": "Australia vs New Zealand",
        "bettingOutcomeLabel": "Australia",
        "sportXEventId": "L6350002",
        "bettingOutcome": 1,
        "gameTime": "2021-01-06T23:30:00.000Z",
        "leagueLabel": "International Test",
        "outcome": 0,
        "netReturn": "4.999999999999999999"
      },
      {
        "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "tradeStatus": "SUCCESS",
        "bettor": "0x025b9FD9F1ed818d688e19c5F43c500bf44cEA47",
        "totalStake": "4.999999999999999999",
        "weightedAverageOdds": "67564738292011016000",
        "marketHash": "0x7b27d766b6e36b87e2b7924f792c4f84d9b459318735d69cbb7d695b0857f8d5",
        "maker": false,
        "settled": false,
        "fillHash": "0xc44e72f94e70ede9742c6b8aeb77e743e0cbf753ff3eae8bd4f1ab98b9260f0e",
        "gameLabel": "Australia vs New Zealand",
        "bettingOutcomeLabel": "Australia",
        "sportXEventId": "L6350002",
        "bettingOutcome": 1,
        "gameTime": "2021-01-06T23:30:00.000Z",
        "leagueLabel": "International Test",
        "outcome": 0,
        "netReturn": "4.999999999999999999"
      }
    ],
    "count": 100
  }
}

This endpoint retrieves past consolidated trades on the exchange via pagination. If a trade fills multiple orders, it will show up as one entry here per bettor.

HTTP Request

GET https://api.sx.bet/trades/consolidated

Query parameters

Name Required Type Description
bettor false string Only get trades placed by this bettor
settled true boolean If true only get settled trades
page true number Page number for pagination
perPage true number Amount of records to fetch per page
sortBy true string Which field to sort by (see response for field names)
sortAsc true boolean If true, sorts in ascending order
maker false boolean If true, only gets trades where the bettor was a market maker
sportXEventId false string Only gets trades for this event ID
tradeStatus false string Filter trades to see only those with SUCCESS or FAILED status'
chainVersion false string Must be either SXN or SXR.
If not passed, data from both chains are returned. See migration docs

Response format

Name Type Description
status string success or failure if the request succeeded or not
data any The response data
> trades ConsolidatedTrade[] The consolidated trades for this request
> count number Total count of trades for this query

A ConsolidatedTrade object has the following format

Name Type Description
baseToken string The token in which this trade was placed
tradeStatus string SUCCESS or FAILED depending on if this trade succeeded or not
bettor string The address of the bettor who placed the trade
totalStake string Total nominal stake of the trade
weightedAverageOdds string Weighted average odds (based on stake) of the trade if the trade filled multiple orders. Divide by 10^20 to get the odds in decimal format.
marketHash string The unique identifier of the market for which this trade was placed
maker boolean true if the bettor is market maker in this trade
settled boolean true if this bet is settled (this refers to if the bet was won lost or voided, not if the trade succeeded or not)
fillHash string The unique identifier for this trade
gameLabel string A general label for the market
bettingOutcomeLabel string Which team/side the bettor bet
sportXEventId string The unique fixture ID for this trade
bettingOutcome number 1 if the bettor is betting outcome one, 2 otherwise
gameTime string ISO formatted date string of when the game is suppossed to occur
leagueLabel string The name of this league
outcome number With settled=true, this will be 0, 1, or 2 depending on the final outcome of the market
chainVersion string SXN or SXR. See migration docs.
sportXeventId string The event related to this trade

Orders

Get active orders

curl --location --request GET 'https://api.sx.bet/orders'

The above command returns JSON structured like this

{
  "status": "success",
  "data": [
    {
      "fillAmount": "0",
      "orderHash": "0xb46e5fff6498f061e93c4f5ed501ee72d924180d6aa78cdfd4d188d3383c91d4",
      "marketHash": "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
      "maker": "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
      "totalBetSize": "10000000000000000000",
      "percentageOdds": "70455284072443640000",
      "expiry": 2209006800,
      "apiExpiry": 1631233201
      "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
      "executor": "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
      "salt": "69415402816762328320330277846098411244657139277332120954321492419616371539163",
      "isMakerBettingOutcomeOne": true,
      "signature": "0x2aaea5b7c86166c0fbf2745c66aff794a23d21ac71ee143d08706700adbb59aa4c9b862286cf736acae5a74b10847ced73b628f4396eaab0af13b0c637fe4d021b",
      "createdAt": "2021-06-04T17:42:07.257Z"
    },
    {
      "fillAmount": "0",
      "orderHash": "0xd055d177477bd19faa9bad5cb4f907d8ebe069b614bb708713de068293cb809d",
      "marketHash": "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
      "maker": "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
      "totalBetSize": "10000000000000000000",
      "percentageOdds": "29542732332840140000",
      "expiry": 2209006800,
      "apiExpiry": 1631233201,
      "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
      "executor": "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
      "salt": "37069036490382455296196784649228360571791475783443923366499720348790829992442",
      "isMakerBettingOutcomeOne": false,
      "signature": "0x68fb16ff440c65e0306cb16a9842da362480208a9a75597d45ca722769d93e6a13c9196a2654f764bfd5c0d4c83165c0a8ffa955ae9af8afd17544b0db29eaf71c",
      "createdAt": "2021-06-04T17:42:07.303Z"
    },
    {
      "fillAmount": "0",
      "orderHash": "0xa5a30ca2251ac1431adf3d88f5734a53b78a13b2c707211eb83996bf099e3973",
      "marketHash": "0x15c5cceb3d27518241355e9f148ef96b0a178f1bcdb366dea2d0e621a9cef1fb",
      "maker": "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
      "totalBetSize": "10000000000000000000",
      "percentageOdds": "50000000000000000000",
      "expiry": 2209006800,
      "apiExpiry": 1631233201,
      "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
      "executor": "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
      "salt": "90344661128498016788545482097709376028896473001963632493180076229973632520043",
      "isMakerBettingOutcomeOne": true,
      "signature": "0x9b6c1e1d0cae584a3078f20d7bafd2031467a1cbce905027c6c970c13edef4357eeac625afe869417ffbecfb67b6672b765177d42fbda777f6b1a00962da2cc61c",
      "createdAt": "2021-06-04T17:42:07.270Z"
    }
  ]
}

This endpoint returns active orders on the exchange based on a few parameters

HTTP Request

GET https://api.sx.bet/orders

Query parameters

Name Required Type Description
marketHashes false string[] Only get orders for these market hashes. Comma separated.
baseToken false string Only get orders denominated in this base token
maker false string Only get orders for this market maker
sportXEventId false string Only get orders for this event ID
chainVersion false string Must be either SXN or SXR.
If not passed, data from both chains are returned. See migration docs

Response format

Name Type Description
fillAmount string How much this order has been filled in Ethereum units up to a max of totalBetSize. See the token section of how to convert this into nominal amounts
orderHash string A unique identifier for this order
marketHash string The market corresponding to this order
maker string The market maker for this order
totalBetSize string The total size of this order in Ethereum units. See the the token section section for how to convert this into nominal amounts.
percentageOdds string The odds that the maker receives in the sx.bet protocol format. To convert to an implied odds divide by 10^20. To convert to the odds that the taker would receive if this order would be filled in implied format, use the formula takerOdds=1-percentageOdds/10^20. See the unit conversion section for more details.
expiry number Deprecated: the time in unix seconds after which this order is no longer valid. After deprecation, this field is always 2209006800 (2040)
apiExpiry number The time in unix seconds after which this order is no longer valid.
baseToken string The base token this order is denominated in
executor string The address permitted to execute on this order. This is set to the sx.bet exchange
salt string A random number to differentiate identical orders
isMakerBettingOutcomeOne boolean true if the maker is betting outcome one (and hence taker is betting outcome two if filled)
signature string Signature of the maker on this order
sportXeventId string The event related to this order

Error Responses

Error Code Description
RATE_LIMIT_ORDER_REQUEST_MARKET_COUNT More than 1000 marketHashes queried
BOTH_SPORTXEVENTID_MARKETHASHES_PRESENT Can only send one of marketHashes or sportXEventId

Enabling betting

import { MaxUint256 } from "ethers/constants";
import { Contract } from "ethers";
import { JsonRpcProvider } from "ethers/providers";

const walletAddress = process.env.WALLET_ADDRESS;
const tokenAddress = process.env.TOKEN_ADDRESS;
const tokenTransferProxyAddress = process.env.TOKEN_TRANSFER_PROXY_ADDRESS;
const provider = new providers.JsonRpcProvider(process.env.RPC_URL); // find this under the 'references' section
const wallet = new Wallet(process.env.PRIVATE_KEY).connect(provider);
const tokenContract = new Contract(
  tokenAddress,
  [
    {
      constant: false,
      inputs: [
        { internalType: "address", name: "usr", type: "address" },
        { internalType: "uint256", name: "wad", type: "uint256" },
      ],
      name: "approve",
      outputs: [{ internalType: "bool", name: "", type: "bool" }],
      payable: false,
      stateMutability: "nonpayable",
      type: "function",
    },
  ],
  wallet
);
await tokenContract.approve(tokenTransferProxyAddress, MaxUInt256, {
  gasLimit: 100000,
});

To enable betting (filling or posting orders), you need to approve the TokenTransferProxy contract for each token for which you wish to trade. Otherwise, any endpoints that create/cancel or fill orders will fail. For example if you want to trade with both ETH and USDC, you'll need to approve the contract twice, once for each token. The address of the TokenTransferProxy is available at https://api.sx.bet/metadata and the address of each token is given in the tokens section

If you don't wish to do this programmatically, you can simply go to https://sx.bet, make a test bet with the account and token you'll be using, and you will be good to go.

If you want to do it programmatically, see the code sample on the right. Note you will need a tiny bit of SX to make this transaction.

Post a new order

curl --location --request POST 'https://api.sx.bet/orders/new' \
--header 'Content-Type: application/json' \
--data-raw '{
    "orders": [
        {
            "marketHash": "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
            "maker": "0x6F75bA6c90E3da79b7ACAfc0fb9cf3968aa4ee39",
            "totalBetSize": "21600000000000000000",
            "percentageOdds": "47846889952153115000",
            "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
            "apiExpiry": 1631359800,
            "expiry": 2209006800,
            "executor": "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
            "isMakerBettingOutcomeOne": true,
            "signature": "0x50b00e7994b0656f78701537296444bccba2a7e4d46a84ff26c8ca48cb66774c76faa893be293412959779900232065c8236e489158070777d7a3e1a37d911811b",
            "salt": "61882422358902283358380622686147595792242782952753619716150366288606659190035"
        }
    ]
}'
import { BigNumber, utils, providers, Wallet } from "ethers";

const order = {
  marketHash:
    "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
  maker: "0x6F75bA6c90E3da79b7ACAfc0fb9cf3968aa4ee39",
  totalBetSize: BigNumber.from("21600000000000000000").toString(),
  percentageOdds: BigNumber.from("47846889952153115000").toString(),
  baseToken: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
  apiExpiry: 1631233201,
  expiry: 2209006800,
  executor: "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
  isMakerBettingOutcomeOne: true,
  salt: BigNumber.from(utils.randomBytes(32)).toString(),
};

const orderHash = utils.arrayify(
  utils.solidityKeccak256(
    [
      "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,
    ]
  )
);

// Example shown here with an ethers.js wallet if you're interacting with the exchange using a private key
const wallet = new Wallet(process.env.PRIVATE_KEY);
const signature = await wallet.signMessage(orderHash);

// Example shown here if you're interacting with the exchange using an injected web3 provider such as metamask
const provider = new providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const signature = await signer.signMessage(orderHash);

const signedOrder = { ...order, signature };

const result = await fetch("https://api.sx.bet/orders/new", {
  method: "POST",
  body: JSON.stringify({ orders: [signedOrder] }),
  headers: { "Content-Type": "application/json" },
});

// Odds ladder testing

import { BigNumber } from "bignumber.js";
import { BigNumber as EthBigNumber } from "ethers";

export const ODDS_LADDER_STEP_SIZE = 25; // (0.1% = 1, 0.5% = 5, etc)

/**
 * Check if the odds are valid, i.e., in one of the allowed steps
 * @param odds Odds to check
 */
export function checkOddsLadderValid(
  odds: EthBigNumber,
  stepSizeOverride?: number
) {
  // Logic:
  // 100% = 10^20
  // 10% = 10^19
  // 1% = 10^18
  // 0.1% = 10^17
  return odds
    .mod(EthBigNumber.from(10).pow(16).mul(ODDS_LADDER_STEP_SIZE))
    .eq(0);
}

/**
 * Rounds odds to the nearest step.
 * @param odds Odds to round.
 */
export function roundDownOddsToNearestStep(
  odds: EthBigNumber,
  stepSizeOverride?: number
) {
  const step = EthBigNumber.from(10).pow(16).mul(ODDS_LADDER_STEP_SIZE);
  const bnStep = new BigNumber(step.toString());
  const bnOdds = new BigNumber(odds.toString());
  const firstPassDivision = bnOdds.dividedBy(bnStep).toFixed(0, 3);
  return EthBigNumber.from(firstPassDivision).mul(step);
}

The above command returns JSON structured like this

{
  "status": "success",
  "data": {
    "orders": [
      "0x7a9d420551c4a635849013dd908f7894766e97aee25fe656d0c5ac857e166fac"
    ]
  }
}

This endpoint offers new orders on the exchange (market making). Offering orders does not cost any fee.

Note you can offer as many orders as you wish, provided your total exposure for each token (as measured by totalBetSize - fillAmount) remains under your wallet balance. If your wallet balance dips under your total exposure, orders will be removed from the book until it reaches the minimum again.

To offer bets on sx.bet via the API, make sure you first enable betting by following the steps here.

We enforce an odds ladder to prevent diming. Your offer, in implied odds, must fall on one of the steps on the ladder. Currently, that is set to intervals of 0.25%, meaning that your offer cannot fall between the steps. An offer of 50.25% would be valid, but an offer of 50.05% would not. You can check if your odds would fall on the ladder by taking the modulus of your odds and 2.5 * 10 ^ 17 and checking if it's equal to 0. See the bottom of the JavaScript tab for a sample on how to do this, and how to round your odds to the nearest step.

You can get the current interval from GET /metadata. It will spit out a number from 10 to 100, where 10 = 0.10%, and 25 = 0.25%

HTTP Request

POST https://api.sx.bet/orders/new

Request payload parameters

Name Required Type Description
orders true SignedNewOrder[] The new orders to post

A SignedNewOrder object looks like this

Name Type Description
marketHash string The market you wish to place this order under
maker string The ethereum address offering the bet
baseToken string The token this order is denominated in
totalBetSize string The total bet size of the order in Ethereum units.
percentageOdds string The odds the maker will be receiving as this order gets filled. Must be on the odds ladder or will be rejected.
expiry number Deprecated. Time in UNIX seconds after which this order is no longer valid. Must always be 2209006800.
apiExpiry number Time in UNIX seconds after which this order is no longer valid.
executor string The sx.bet executor address. See the metadata section for where to get this address
salt string A random 32 byte string to differentiate between between orders with otherwise identical parameters
isMakerBettingOutcomeOne boolean true if the maker is betting outcome one (and hence taker is betting outcome two if filled)
signature string The signature of the maker on this order payload

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> orders string[] The order hashes corresponding to the new orders

Error Responses

Error Code Description
TOO_MANY_DIFFERENT_MARKETS More than 3 different markets queried
ORDERS_MUST_HAVE_IDENTICAL_MARKET All orders must be for the same network, either SXN or SXR
BAD_BASE_TOKEN All orders must be for the same base token, either USDC or WSX

Cancel individual orders

curl --location --request POST 'https://api.sx.bet/orders/cancel/v2' \
--header 'Content-Type: application/json' \
--data-raw '{
    "orderHashes": [
        "0x335d3dbd0621f0f6da90d1a58269e71b2fb5e91193dca75a0b90396cccb63001"
    ],
    "signature": "0x1763cb98a069657cb778fdc295eac48741b957bfe58e54f7f9ad03c6c1ca3d053d9ca2e6957af794991217752b69cb9aa4ac9330395c92e24c8c25ec19220e5a1b",
    "salt": "0x6845028402f518a1c90770554a71017cd434ae9f2c09aa56c9560835c1929650",
    "maker": "0xe087299AE9Acd0133d6D1544A97Bb0EEe24a2671",
    "timestamp": 1643897553
}'
import { signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";
import { randomBytes } from "@ethersproject/random";

// Example is shown using a private key

const privateKey = process.env.PRIVATE_KEY;
const bufferPrivateKey = Buffer.from(privateKey.substring(2), "hex");
const orderHashes = [
  "0x4ead6ef92741cd0b6e1ea32cb1d9586a85165e8bd780ab6f897992428c357bf1",
];
const salt = `0x${Buffer.from(randomBytes(32)).toString("hex")}`;
const timestamp = Math.floor(new Date().getTime() / 1000);
const wallet = new Wallet(privateKey);

function getCancelOrderEIP712Payload(orderHashes, salt, timestamp, chainId) {
  const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "salt", type: "bytes32" },
      ],
      Details: [
        { name: "orderHashes", type: "string[]" },
        { name: "timestamp", type: "uint256" },
      ],
    },
    primaryType: "Details",
    domain: {
      name: "CancelOrderV2SportX",
      version: "1.0",
      chainId,
      salt,
    },
    message: { orderHashes, timestamp },
  };
  return payload;
}

const payload = getCancelOrderEIP712Payload(orderHashes, salt, timestamp, chainId);

const signature = signTypedData({
  privateKey: bufferPrivateKey, 
  data: payload,
  version: SignTypedDataVersion.V4,
});

const apiPayload = {
  signature,
  orderHashes,
  salt,
  maker: wallet.address,
  timestamp,
};

const result = await fetch("https://api.sx.bet/orders/cancel/v2", {
  method: "POST",
  body: JSON.stringify(apiPayload),
  headers: { "Content-Type": "application/json" },
});

The above command returns json structured like this

{
  "status": "success",
  "data": {
    "cancelledCount": 1
  }
}

This endpoint cancels existing orders on the exchange that you placed as a market maker. If passed orders that do not exist, they simply fail silently while the others will succeed.

HTTP Request

POST https://api.sx.bet/orders/cancel/v2

Query parameters

Name Required Type Description
chainVersion false string Must be either SXN or SXR.
If not passed, Default will be SXN. See migration docs

Request payload parameters

Name Required Type Description
orderHashes true string[] The order hashes to cancel
signature true string The EIP712 signature on the cancel order payload. See the EIP712 signing section for more details on how to compute this signature.
salt required string A random 32 bytes hex string to protect against replay
maker required true The account from which you are cancelling orders
timestamp required true The current timestamp in UNIX seconds to protect against replay

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> cancelledCount string[] How many orders were cancelled, of the orders passed

Error Responses

Error Code Description
CANCEL_REQUEST_ALREADY_PROCESSED This cancellation is already processed

Cancel event orders

curl --location --request POST 'https://api.sx.bet/orders/cancel/event' \
--header 'Content-Type: application/json' \
--data-raw '{
    "sportXEventId": "L1234123",
    "signature": "0x1763cb98a069657cb778fdc295eac48741b957bfe58e54f7f9ad03c6c1ca3d053d9ca2e6957af794991217752b69cb9aa4ac9330395c92e24c8c25ec19220e5a1b",
    "salt": "0x6845028402f518a1c90770554a71017cd434ae9f2c09aa56c9560835c1929650",
    "maker": "0xe087299AE9Acd0133d6D1544A97Bb0EEe24a2671",
    "timestamp": 1643898624
}'
import { signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";
import { randomBytes } from "@ethersproject/random";
import { Wallet } from "@ethersproject/wallet";

// Example is shown using a private key

const privateKey = process.env.PRIVATE_KEY;
const bufferPrivateKey = Buffer.from(privateKey.substring(2), "hex");
const sportXEventId = "L1231231";
const salt = `0x${Buffer.from(randomBytes(32)).toString("hex")}`;
const timestamp = Math.floor(new Date().getTime() / 1000);
const wallet = new Wallet(privateKey);

function getCancelOrderEventsEIP712Payload(
  sportXEventId,
  salt,
  timestamp,
  chainId
) {
  const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "salt", type: "bytes32" },
      ],
      Details: [
        { name: "sportXEventId", type: "string" },
        { name: "timestamp", type: "uint256" },
      ],
    },
    primaryType: "Details",
    domain: {
      name: "CancelOrderEventsSportX",
      version: "1.0",
      chainId,
      salt,
    },
    message: { sportXEventId, timestamp },
  };
  return payload;
}

const payload = getCancelOrderEventsEIP712Payload(
  sportXEventId,
  salt,
  timestamp,
  chainId
);

const signature = signTypedData({
  privateKey: bufferPrivateKey,
  data: payload,
  version: SignTypedDataVersion.V4,
});

const apiPayload = {
  signature,
  sportXEventId,
  salt,
  maker: wallet.address,
  timestamp,
};

const result = await fetch("https://api.sx.bet/orders/cancel/v2", {
  method: "POST",
  body: JSON.stringify(apiPayload),
  headers: { "Content-Type": "application/json" },
});

The above command returns json structured like this

{
  "status": "success",
  "data": {
    "cancelledCount": 1
  }
}

This endpoint cancels existing orders on the exchange for a particular event that you placed as a market maker.

HTTP Request

POST https://api.sx.bet/orders/cancel/event

Query parameters

Name Required Type Description
chainVersion false string Must be either SXN or SXR.
If not passed, Default will be SXN. See migration docs

Request payload parameters

Name Required Type Description
sportXEventId true string The event for which orders should be cancelled
signature true string The EIP712 signature on the cancel order payload. See the EIP712 signing section for more details on how to compute this signature.
salt required string A random 32 bytes hex string to protect against replay
maker required true The account from which you are cancelling orders
timestamp required true The current timestamp in UNIX seconds to protect against replay

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> cancelledCount string[] How many orders were cancelled, of the orders passed

Error Responses

Error Code Description
CANCEL_REQUEST_ALREADY_PROCESSED This cancellation is already processed

Cancel all orders

curl --location --request POST 'https://api.sx.bet/orders/cancel/all' \
--header 'Content-Type: application/json' \
--data-raw '{
    "signature": "0x1763cb98a069657cb778fdc295eac48741b957bfe58e54f7f9ad03c6c1ca3d053d9ca2e6957af794991217752b69cb9aa4ac9330395c92e24c8c25ec19220e5a1b",
    "salt": "0x6845028402f518a1c90770554a71017cd434ae9f2c09aa56c9560835c1929650",
    "maker": "0xe087299AE9Acd0133d6D1544A97Bb0EEe24a2671",
    "timestamp": 1643898624
}'
import { signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";
import { randomBytes } from "@ethersproject/random";
import { Wallet } from "@ethersproject/wallet";

// Example is shown using a private key

const privateKey = process.env.PRIVATE_KEY;
const bufferPrivateKey = Buffer.from(privateKey.substring(2), "hex");
const salt = `0x${Buffer.from(randomBytes(32)).toString("hex")}`;
const timestamp = Math.floor(new Date().getTime() / 1000);
const wallet = new Wallet(privateKey);

function getCancelAllOrdersEIP712Payload(salt, timestamp, chainId) {
  const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "salt", type: "bytes32" },
      ],
      Details: [{ name: "timestamp", type: "uint256" }],
    },
    primaryType: "Details",
    domain: {
      name: "CancelAllOrdersSportX",
      version: "1.0",
      chainId,
      salt,
    },
    message: { timestamp },
  };
  return payload;
}

const payload = getCancelOrderEventsEIP712Payload(salt, timestamp, chainId);

const signature = signTypedData({
  privateKey: bufferPrivateKey,
  data: payload,
  version: SignTypedDataVersion.V4,
});

const apiPayload = {
  signature,
  sportXEventId,
  salt,
  maker: wallet.address,
  timestamp,
};

const result = await fetch("https://api.sx.bet/orders/cancel/all", {
  method: "POST",
  body: JSON.stringify(apiPayload),
  headers: { "Content-Type": "application/json" },
});

The above command returns json structured like this

{
  "status": "success",
  "data": {
    "cancelledCount": 10
  }
}

This endpoint cancels ALL existing orders on the exchange that you placed as a market maker.

HTTP Request

POST https://api.sx.bet/orders/cancel/all

Query parameters

Name Required Type Description
chainVersion false string Must be either SXN or SXR.
If not passed, Default will be SXN. See migration docs

Request payload parameters

Name Required Type Description
signature true string The EIP712 signature on the cancel order payload. See the EIP712 signing section for more details on how to compute this signature.
salt required string A random 32 bytes hex string to protect against replay
maker required true The account from which you are cancelling orders
timestamp required true The current timestamp in UNIX seconds to protect against replay.

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> cancelledCount string[] How many orders were cancelled, of the orders passed

Error Responses

Error Code Description
CANCEL_REQUEST_ALREADY_PROCESSED This cancellation is already processed

Filling orders

curl --location --request POST 'https://api.sx.bet/orders/fill' \
--header 'Content-Type: application/json' \
--data-raw '{"orderHashes":["0x863a2288a640e1bb722da2ee7a6f323ea28caee8ac681d320534d0e7f2e849de","0x001f676d68baf85310145f85bc5aeba64108b234607b4fae25c704069ca8464a"],"takerAmounts":["10000000000000000000","10000000000000000000"],"taker":"0xa3bBFaB3645B2Dd4296cADc451d74574CD47Ba1a","takerSig":"0x09d2603a8c8646221d6972b04a5cdd8b13d6326a267329825567a25a5e63606b07b97c84640bfb3ee4a5053083ce178d9e0c9cbdf1b1dfd519fda0594fae30dc1c","fillSalt":"69231297238279245345865414293427982207908612843136003245427437324972455931243","action":"N/A","market":"N/A","betting":"N/A","stake":"N/A","odds":"N/A","returning":"N/A"}'
import { signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";
import {
  BigNumber,
  constants,
  Contract,
  providers,
  utils,
  Wallet,
} from "ethers";
import { randomBytes } from "ethers/lib/utils";
import dayjs from "dayjs";

async function fillOrder() {
  const privateKey = process.env.PRIVATE_KEY;
  const takerAddress = process.env.TAKER_ADDRESS;
  const tokenAddress = process.env.TOKEN_ADDRESS;

  // get the following from https://api.sx.bet/metadata
  const tokenTransferProxyAddress = process.env.TOKEN_TRANSFER_PROXY_ADDRESS;
  const EIP712FillHasherAddress = process.env.EIP712_FILL_HASHER_ADDRESS;
  const chainId = process.env.CHAIN_ID; // 416 in production
  const domainVersion = process.env.DOMAIN_VERSION;

  const bufferPrivateKey = Buffer.from(privateKey!.substring(2), "hex");
  const wallet = new Wallet(privateKey).connect(
    new providers.JsonRpcProvider(process.env.RPC_URL) // find this under the 'references' section
  );
  const takerAmounts = ["10000000000000000000", "10000000000000000000"];
  const fillSalt = BigNumber.from(randomBytes(32)).toString();
  const approvalAmount = constants.MaxUint256;
  const tokenContract = new Contract(
    tokenAddress,
    [
      {
        constant: false,
        inputs: [
          { internalType: "address", name: "usr", type: "address" },
          { internalType: "uint256", name: "wad", type: "uint256" },
        ],
        name: "approve",
        outputs: [{ internalType: "bool", name: "", type: "bool" }],
        payable: false,
        stateMutability: "nonpayable",
        type: "function",
      },
      {
        inputs: [
          {
            internalType: "address",
            name: "owner",
            type: "address",
          },
        ],
        name: "nonces",
        outputs: [
          {
            internalType: "uint256",
            name: "",
            type: "uint256",
          },
        ],
        stateMutability: "view",
        type: "function",
        constant: true,
      },
      {
        inputs: [],
        name: "name",
        outputs: [
          {
            internalType: "string",
            name: "",
            type: "string"
          }
        ],
        stateMutability: "view",
        type: "function",
        constant: true
      }
    ],
    wallet
  );

  let nonce: BigNumber = await tokenContract.nonces(takerAddress);
  const tokenName: string = await tokenContract.name();
  const abiEncodedFunctionSig = tokenContract.interface.encodeFunctionData(
    "approve",
    [tokenTransferProxyAddress, approvalAmount]
  );

  const ordersToFill = [
    {
      fillAmount: "0",
      orderHash:
        "0x863a2288a640e1bb722da2ee7a6f323ea28caee8ac681d320534d0e7f2e849de",
      marketHash:
        "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
      maker: "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
      totalBetSize: "120000000000000000000",
      percentageOdds: "68860772772306080000",
      expiry: 2209006800,
      apiExpiry: 1631233201,
      baseToken: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
      executor: "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
      salt:
        "64542697517744547417546237340530363903382582642857122744382059379852833736063",
      isMakerBettingOutcomeOne: true,
      signature:
        "0xa58255f9f7bb8a2698d66f9931a3d4ebe2e5605299a57d78b7c048f26585fdb5144b16bb930ad564f2e2819f66c8c20c2c076d8951728d8242481d58af221fc51b",
      createdAt: "2021-06-24T21:44:51.355Z",
    },
    {
      fillAmount: "0",
      orderHash:
        "0x001f676d68baf85310145f85bc5aeba64108b234607b4fae25c704069ca8464a",
      marketHash:
        "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
      maker: "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
      totalBetSize: "120000000000000000000",
      percentageOdds: "27138321995464855000",
      expiry: 2209006800,
      apiExpiry: 1631233201,
      baseToken: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
      executor: "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
      salt:
        "33394198022904122521460365691253625674733505262618329029186356280403872887283",
      isMakerBettingOutcomeOne: false,
      signature:
        "0xb52be3279a092823bb46b3bd9a397bef2a06eda95a519718992ea88d73a4375874728cb7c5f6527b67195e44f3f0f0d29956dc05e18e775547d8f2faf3d4bd001c",
      createdAt: "2021-06-24T21:44:51.374Z",
    },
  ];

  const signingPayload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      Details: [
        { name: "action", type: "string" },
        { name: "market", type: "string" },
        { name: "betting", type: "string" },
        { name: "stake", type: "string" },
        { name: "odds", type: "string" },
        { name: "returning", type: "string" },
        { name: "fills", type: "FillObject" },
      ],
      FillObject: [
        { name: "orders", type: "Order[]" },
        { name: "makerSigs", type: "bytes[]" },
        { name: "takerAmounts", type: "uint256[]" },
        { name: "fillSalt", type: "uint256" },
        { name: "beneficiary", type: "address" },
        { name: "beneficiaryType", type: "uint8" },
        { name: "cashOutTarget", type: "bytes32" },
      ],
      Order: [
        { name: "marketHash", type: "bytes32" },
        { name: "baseToken", type: "address" },
        { name: "totalBetSize", type: "uint256" },
        { name: "percentageOdds", type: "uint256" },
        { name: "expiry", type: "uint256" },
        { name: "salt", type: "uint256" },
        { name: "maker", type: "address" },
        { name: "executor", type: "address" },
        { name: "isMakerBettingOutcomeOne", type: "bool" },
      ],
    },
    primaryType: "Details",
    domain: {
      name: "SX Bet",
      version: domainVersion,
      chainId: chainId,
      verifyingContract: EIP712FillHasherAddress,
    },
    message: {
      action: "N/A",
      market: "N/A",
      betting: "N/A",
      stake: "N/A",
      odds: "N/A",
      returning: "N/A",
      fills: {
        makerSigs: ordersToFill.map((order) => order.signature),
        orders: ordersToFill.map((order) => ({
          marketHash: order.marketHash,
          baseToken: order.baseToken,
          totalBetSize: order.totalBetSize.toString(),
          percentageOdds: order.percentageOdds.toString(),
          expiry: order.expiry.toString(),
          salt: order.salt.toString(),
          maker: order.maker,
          executor: order.executor,
          isMakerBettingOutcomeOne: order.isMakerBettingOutcomeOne,
        })),
        takerAmounts,
        fillSalt,
        beneficiary: constants.AddressZero,
        beneficiaryType: 0,
        cashOutTarget: constants.HashZero,
      },
    },
  };

  const approveProxySigningPayload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      Permit: [
        { name: "owner", type: "address" },
        { name: "spender", type: "address" },
        { name: "value", type: "uint256" },
        { name: "nonce", type: "uint256" },
        { name: "deadline", type: "uint256" },
      ],
    },
    domain: {
      name: tokenName,
      version: "1",
      chainId: chainId,
      verifyingContract: tokenAddress,
    },
    message: {
      owner: takerAddress,
      spender: tokenTransferProxyAddress,
      value: approvalAmount,
      nonce: nonce.toNumber(),
      deadline: dayjs().add(2, "hour").unix(),
    },
    primaryType: "Permit",
  };

  const approveProxySignature = signTypedData({
    privateKey: bufferPrivateKey,
    data: approveProxySigningPayload,
    version: SignTypedDataVersion.V4,
  });

  const signature = signTypedData({
    privateKey: bufferPrivateKey,
    data: signingPayload,
    version: SignTypedDataVersion.V4,
  });

  const apiPayload = {
    orderHashes: ordersToFill.map((order) => order.orderHash),
    takerAmounts,
    taker: takerAddress,
    takerSig: signature,
    fillSalt,
    action: "N/A",
    market: "N/A",
    betting: "N/A",
    stake: "N/A",
    odds: "N/A",
    returning: "N/A",
    approveProxyPayload: {
      owner: takerAddress,
      spender: tokenTransferProxyAddress,
      tokenAddress,
      amount: approvalAmount.toString(),
      signature: approveProxySignature,
    },
  };

  const response = await fetch(`https://api.sx.bet/orders/fill`, {
    method: "POST",
    body: JSON.stringify(apiPayload),
    headers: { "Content-Type": "application/json" },
  });
}

The above command returns json structured like this

{
  "status": "success",
  "data": {
    "fillHash": "0x840763ae29b7a6adfa0e315afa47be30cdebd5b793d179dc07dc8fc4f0034965"
  }
}

This endpoint fills orders on the exchange. Multiple orders can be filled at once and no gas is paid as this is a meta transaction submitted by the API itself.

Note that there are built in betting delays based on the below chart. This is added to guard against toxic flow and high spikes in latency from the bookmaker's side. It is effectively protection for the bookmaker. If the odds change within that delay time, the order will be cancelled and an error will be thrown.

PREGAME

Sport Delay ( in seconds )
Default (all sports) 0.5

LIVE

Sport Delay ( in seconds )
Baseball 12
Football 10
Tennis 10
Soccer 10
Basketball 8
Hockey 8
Default (all other) 8

To fill orders on sx.bet via the API, make sure you first enable betting by following the steps here

HTTP Request

POST https://api.sx.bet/orders/fill

Request payload parameters

Name Required Type Description
action true string User facing string for what action the user is taking. Can simply set to "N/A" when using the API
betting true string User facing string for what bet the user is making. Can simply set to "N/A" when using the API.
fillSalt true string Random 32 byte string to identify this fill. Must be the same fillSalt used when computing the EIP712 payload
market true string User facing string for what market the user is betting on. Can simply set to "N/A" when using the API
odds true string User facing string for the odds the user is receiving. Can simply set to "N/A" when using the API
orderHashes true string[] Orders being filled. Must be the order hashes of the orders used in computing the EIP712 payload. Must be the same length as takerAmounts
returning true string User facing string for what the bet wil be returning. Can simply set to "N/A" when using the API.
taker true string Address of the taker taking the bet
stake true string User facing string for how much the user is risking. Can simly set to "N/A" when using the API.
takerAmounts true string[] How much each order is being filled, ordered by index. Must be in the same order as orderHashes, and the same length as orderHashes. It also must be the same and in the same order as the takerAmounts array used when computing the EIP712 payload.
takerSig true string The EIP712 signature of the taker on the payload. See the example of how to compute this.
message true string A user-facing message for the eip712 signing. Can be anything.
approveProxyPayload false ApproveSpenderPayload Extra object required if you wish to atomically ERC20.approve() prior to the bet. This can be useful from a UX point of view if you don't want the user to have to wait until the approval is mined before the bet can be submitted
affiliateAddress false string Set the taker to a valid affiliate's address.

where an ApproveSpenderPayload looks like

Name Required Type Description
owner true string Address of the bettor/taker
spender true string Address of the contract permitted to spend tokens on behalf of the bettor/taker to bet. See the TokenTransferProxy address in the available at https://api.sx.bet/metadata for this address
tokenAddress true string Address of the token being used to bet. Must be the same as the token referenced in the orders being filled
amount true string Amount of tokens to approve. Must be greater than or equal to the total tokens bet from the bettors's perspective, i.e., how many tokens are leaving the bettor's wallet
signature true string The EIP712 signature of the this payload.

Response format

Name Type Description
status string success or failure if the request succeeded or not
data object The response data
> fillHash string A unique identifier for this fill.

Error Responses

Error Code Description
ORDERS_NOT_UNIQUE Only unique orderHashes should be sent
INCORRECT_ARRAY_LENGTHS orderHashes and takerAmounts arrays are different lengths.
ORDERS_DONT_EXIST One of the orders do not exist
AFTER_ORDER_EXPIRY One of the orders have expired
BASE_TOKENS_NOT_SAME All orders must be for the same baseToken
MARKETS_NOT_SAME All orders must be for the same market
DIRECTIONS_NOT_SAME All orders must be betting on the same side isMakerBettingOutcomeOne
INVALID_ORDERS Order is now inactive
MATCH_STATE_INVALID The fixture for the order is in an invalid state and is not bettable anymore
META_TX_RATE_LIMIT_REACHED Cannot have more than 10 meta transactions at once

Unit Conversion

Tokens

Every token in Ethereum has an associated "decimals" value. This effectively specifies how divisible the token is. For example, 100 USDC is actually stored as 100 * 10^18 USDC on Ethereum itself. Here is a table for the tokens supported by SX.bet and their associated decimals value

Token SX Network Address Decimals
USDC See https://api.sx.bet/metadata for address 6
WSX See https://api.sx.bet/metadata for address 18

To convert from nominal units (such as 100 USDC) to Ethereum units which are used in the API, you can do the following.

ethereumAmount = nominalAmount * 10^decimals

Similarly, to convert from Ethereum units to nominal units, you can do the following

nominalAmount = ethereumAmount / 10^decimals

where decimals is specified in the above table.

Odds

Odds are specified in an implied odds format like 8391352143642350000. To convert to a readable implied odds, divide by 10^20. 8391352143642350000 for example is 0.0839 or 8.39%

To convert from implied odds to decimal odds, inverse the number. For example, 0.0839 in decimal format is 1/0.0839 = 11.917.

Bookmaker odds

{
  "fillAmount": "0",
  "orderHash": "0xb46e5fff6498f061e93c4f5ed501ee72d924180d6aa78cdfd4d188d3383c91d4",
  "marketHash": "0x0eeace4a9bbf6235bc59695258a419ed3a05a2c8e3b6a58fb71a0d9e6b031c2b",
  "maker": "0x63a4491dC73245E181c47BAe0ae9d6627E56dE55",
  "totalBetSize": "10000000000000000000",
  "percentageOdds": "70455284072443640000",
  "expiry": 2209006800,
  "apiExpiry": 1631233201
  "baseToken": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
  "executor": "0x3E91041b9e60C7275f8296b8B0a97141e6442d49",
  "salt": "69415402816762328320330277846098411244657139277332120954321492419616371539163",
  "isMakerBettingOutcomeOne": true,
  "signature": "0x2aaea5b7c86166c0fbf2745c66aff794a23d21ac71ee143d08706700adbb59aa4c9b862286cf736acae5a74b10847ced73b628f4396eaab0af13b0c637fe4d021b",
  "createdAt": "2021-06-04T17:42:07.257Z"
}

It's important to note how odds are displayed on sx.bet. Recall from the order section that percentageOdds is from the perspective of the market maker. The odds that are displayed on sx.bet in the order books are what the taker will be receiving. Let's run through an example.

Suppose an order looks like the one on the right.

Here the maker is betting outcome one (isMakerBettingOutcomeOne = true) and receiving implied odds of 70455284072443640000 / 10^20 = 0.704552841. Therefore the taker is betting outcome two and receiving implied odds of 1 - 0.704552841 = 0.295447159. This would be displayed on sx.bet (what the user sees) under the second order book with odds of 29.5% in implied format, or 1 / 0.295447159 = 3.3847 in decimal format.

bookmaker_odds_example

EIP712 Signing

For certain operations with the API, we require you to sign data using the EIP712 specification. We provide two examples to sign data in the EIP712 fashion in JavaScript if you're using an injected provider (such as MetaMask or another wallet) or a private key.

As of now we have not tested other libraries in other languages that support EIP712.

Private key signing example

import { signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";

const privateKey = process.env.PRIVATE_KEY
// Assuming process.env.PRIVATE_KEY is "0x"-prefixed
const bufferPrivateKey = Buffer.from(privateKey.substring(2), "hex");
const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" }
      ],
      Details: [
        { name: "message", type: "string" },
        { name: "orders", type: "string[]" }
      ]
    },
    primaryType: "Details",
    domain: {
      name: "CancelOrderSportX",
      version: "1.0",
      chainId: 416
    },
    message: {
        message: "Are you sure you want to cancel these orders"
        orders: ["0x550128e997978495eeae503c13e2e30243d747e969c65e1a0b565c609e097506"]
    }
};
const signature = signTypedData({
    privateKey: bufferPrivateKey,
    data: payload,
    version: SignTypedDataVersion.V4,
});

Here we use a private key directly which is the most straightforward way to sign data and does not require access to an authenticated node. It's also the fastest.

Injected provider signing example

import { providers } from "ethers";

const walletAddress = "0xa1e32a027271f7d3d5c629b1c87289ccf9611533"
const provider = new providers.Web3Provider(window.ethereum)
const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" }
      ],
      Details: [
        { name: "message", type: "string" },
        { name: "orders", type: "string[]" }
      ]
    },
    primaryType: "Details",
    domain: {
      name: "CancelOrderSportX",
      version: "1.0",
      chainId: 416
    },
    message: {
        message: "Are you sure you want to cancel these orders"
        orders: ["0x550128e997978495eeae503c13e2e30243d747e969c65e1a0b565c609e097506"]
    }
};
const signature = await provider.send("eth_signTypedData_v4", [walletAddress, JSON.stringify(payload)])

Here we use ethers.js and show an example with MetaMask but provided the injected provider has a send() function and supports the eth_signTypedData JSON-RPC method, it will work. Note that in some wallets, such as metamask, eth_signTypedData_v4 is the most up to date version of eth_signTypedData.

Fees

Trading

Fees are currently 0% for makers and takers.