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:
- Update token addresses for
USDC
andWSX
with the new token addresses - Update rest API calls as shown here
- Update websocket streams handlers for orders, trades, and markets (they will now include
chainVersion
) - Migrate funds from SXN to SXR using our bridge
- Configure the payloads with the new
chainId
,domainVersion
and other configs found here and/or here - If you are filling orders programattically , you must change the contract ABI (two new fields under FillObject type)
- Reenable betting for each token as described here
- Start using API as normal for SXR Markets
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 Popular SXR Markets
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:
@metamask/eth-sig-util
: 7.0.3ethers
: 5.7.2
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
- A new market is added
- A market is removed (set to
INACTIVE
) - A market's fields have changed (for example, game time has changed or the market has settled)
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
- Visit sx.bet and register/login to your account. You can connect your MetaMask wallet, or login using your Fortmatic email address.
- If using MetaMask,
sign
the Signature Request. - Click the
Account
tab on the top navigation bar. - Click the
Overview
tab on the account navigation bar. - You will see an
API Credentials
card. Complete theEnhanced Verification
if you have not yet done so by clicking the link in the card. - 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. - 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 |
Popular markets
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.
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.