Markets and Outcomes
In the Azuro protocol, data on markets and outcomes cannot be accessed directly. Instead, the protocol employs
conditions
. Each condition represents a distinct market and contains information about its corresponding outcomes.
You can acquire a list of conditions by sending GraphQL or RPC requests to the Core contract. After obtaining the conditions list, iterate through it to extract the pertinent market and outcome information.
Get market and other names
Each condition
has two outcomeId
values, representing the outcomes for the bet. Each outcomeId
is associated with
a map of other IDs that are related to the data of the corresponding outcome.
By using the outcomeId, you can access an object that includes a list of IDs. These IDs correspond to specific data related to the corresponding outcome. The list of IDs includes the following:
marketId
gamePeriodId
gameTypeId
gameVarietyId
pointsId
selectionId
teamPlayerId
Each of these IDs has a corresponding text representation. For instance, the gamePeriodId: 1
corresponds to "Whole game"
.
In Azuro, we have created a dedicated space called dictionaries
where you can find all the information related to outcomes.
This feature has been implemented to assist in understanding the list of IDs related to each outcomeId
. Take a look
of source files here (opens in a new tab).
For example, the outcomes.json
file provides the related IDs and can be utilized in the following way:
const outcomeId = '1'
const { marketId, gamePeriodId, gameTypeId, gameVarietyId, pointsId, selectionId, teamPlayerId } = outcomes[outcomeId]
gamePeriods[gamePeriodId] // "Whole game"
gameTypes[gameTypeId] // "Goal"
gameVarieties[gameVarietyId] // "Main game"
points[pointsId] // undefined (pointsId for this outcomeId is NULL)
selections[selectionId] // "Yes"
teamPlayers[teamPlayerId] // undefined (teamPlayerId for this outcomeId is NULL)
However, it's important to note that marketId
doesn't have a file from which the market name can be derived from the
ID alone. This is because each market can relate to different outcomes with different game periods and types. Therefore,
to understand which market is related to a specific outcomeId
, a special ID is required. You can check the marketNames.js
and marketDescriptions.js
files, which contain keys different from simple IDs. This key is a combination of other IDs,
including marketId-gamePeriodId-gameTypeId[-teamPlayerId]
.
To make it even easier for developers, we have also created an NPM package that contains a CLI and helper functions
for working with the dictionaries repository. The CLI allows you to download the latest version of the repository, and
the helper functions make it easier to retrieve the text representation of IDs related to a specific outcomeId. Take a
look of @azuro-org/dictionaries
(opens in a new tab).
Aggregate outcomes by markets
Now we know how to get market ID which has related outcomes. Using this knowledge you can aggregate outcomes from conditions and get market name with related outcomes. Let's start with real code example
query Game($id: String!) {
game(id: $id) {
conditions {
conditionId
status
isExpressForbidden
outcomes {
outcomeId
currentOdds
}
}
}
}
It's important to use status
field for smooth UX. You can filter or just disable outcome buttons for paused/canceled conditions.
Pre-match bets are only accepted for status = "Created"
.
Some conditions have restrictions and cannot be used in a combo bet, for example, eSports conditions.
To avoid this error you need check the isExpressForbidden
field in condition subgraph data,
if this field is true than you should forbid such combo bet and show warning notification in your UI.
Here we receive a list of conditions with related outcomes. After this we need to get the marketKey
for each outcome.
Using this key we can group outcomes.
import { dictionaries, getMarketKey } from '@azuro-org/dictionaries'
const outcomesByMarkets = {}
conditions.forEach(({ conditionId, outcomes }) => {
outcomes.forEach(({ outcomeId, currentOdds }) => {
// for support old versions of dictionaries use outcomes dictionary as white-list for outcomes
if (!dictionaries.outcomes[outcomeId]) {
return
}
const marketKey = getMarketKey(outcomeId)
const outcome = {
conditionId,
outcomeId,
currentOdds,
}
if (!outcomesByMarkets[marketKey]) {
outcomesByMarkets[marketKey] = []
}
outcomesByMarkets[marketKey].push(outcome)
})
})
The markets and outcomes related to them can be used to create a data array that can be rendered as outcome buttons on a game page
import { getMarketName } from '@azuro-org/dictionaries'
Object.keys(outcomesByMarkets).forEach((marketKey) => {
const marketName = getMarketName({ marketKey })
const outcomes = outcomesByMarkets[marketKey]
})
Group and sort outcomes
To improve the UX, it's recommended to group the available outcomes within each market and sort them in a meaningful way. To better illustrate the concept of grouping and sorting outcomes within a market, let's use Handicap as an example.
import { dictionaries } from '@azuro-org/dictionaries'
type Outcome = {
conditionId: string
outcomeId: string
}
type FinalMarket = {
marketKey: string
outcomes: Outcome[][]
}
const finalMarketsMap: Record<string, FinalMarket> = {}
// sort by outcomeId and group by conditionId
Object.keys(outcomesByMarkets).forEach((marketKey) => {
finalMarketsMap[marketKey] = {
marketKey,
outcomes: null,
}
const outcomes = outcomesByMarkets[marketKey]
// sort the outcomes by `selectionId` (outcome's selection reference)
outcomes.sort((a, b) => {
const left = dictionaries.outcomes[a.outcomeId].selectionId
const right = dictionaries.outcomes[b.outcomeId].selectionId
return left - right
})
// "Full Time Result" and "Double Chance" are the markets whose outcomes don't require sorting
const MARKETS_THAT_DONT_NEED_GROUPING = [ 1, 2 ]
const marketId = marketKey.split('-')[0]
if (MARKETS_THAT_DONT_NEED_GROUPING.includes(+marketId)) {
// it's worth noting that the outcomes are wrapped within an array here due to the "rows" that are presented below
finalMarketsMap[marketKey].outcomes = [ outcomes ]
}
else {
// group the outcomes by condition ID, which will allow us to display the draw outcomes in separate rows
//
// Handicap:
// H1 (-0.5) H2 (0.5)
// H1 (0.5) H2 (-0.5)
const outcomesByConditionId: Record<string, Outcome[]> = {}
outcomes.forEach((outcome) => {
const key = outcome.conditionId
if (!outcomesByConditionId[key]) {
outcomesByConditionId[key] = []
}
outcomesByConditionId[key].push(outcome)
})
const rows: Outcome[][] = Object.values(outcomesByConditionId)
finalMarketsMap[marketKey].outcomes = rows.sort((a, b) => {
const { points, outcomes } = dictionaries
/*
sort by point in first outcome
Handicap
Team 1 (-2.5) Team 2 (2.5)
Team 1 (-1.5) Team 2 (1.5)
Total Goals
Over (1.5) Under (1.5)
Over (2.5) Under (2.5)
*/
const aFirstOutcome = +points[outcomes[a[0].outcomeId].pointsId]
const bFirstOutcome = +points[outcomes[b[0].outcomeId].pointsId]
return aFirstOutcome - bFirstOutcome
})
}
})
const finalMarkets = Object.values(finalMarketsMap)
Handicap - this is a bet where an artificial advantage is given to one of the teams. E.g. if 'Team 1' has Handicap +1, it means that the final result of the game is determined after adding 1 goal to the number of goals scored by 'Team 1'.
The example provided demonstrates how you can group and sort outcomes. You can use aggregateOutcomesByMarkets
from @azuro-org/toolkit
(opens in a new tab) package to do the same grouping and sorting.