Developer Hub
🔮 For applications
Guides & Tutorials
Advanced integration
Prematch
Prepare Game Markets

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.