05/24 Combo Bets
Azuro Protocol recently launched a new type of bet called combo bets.
The combo bet is a type of bet that enables users to combine outcomes from different conditions and place them as a single bet. This bet wins only if each of the selected outcomes in the set wins, with the payout coefficient being the product of the selected odds, taking into account the applied margin.
One of the advantages of combo bets is that users can get higher odds. In contrast, betting apps can get a higher margin from the bets made by users. This is because a combo bet contains the margin of all the included sub-bets on ordinaries, and it also increases turnover and profits by attracting players to combo bets.
Contracts
Change Log
Azuro Factory now offers support for a new BettingEngine called BetExpress.
With BetExpress, users can place combo bets on events from one of the previously connected PrematchCore instances. The operation of the BetExpress contract generally does not affect the operation of PrematchCore, except that the odds in the PrematchCore also change as a result of making combo bets, but more subtly compared to if the bet were placed on one of the PrematchCore Conditions.
Migration Guide
To connect BetExpress to your pool, perform the following four simple steps:
-
The pool owner must call the
plugExpress
method of the Azuro Factory smart contract and pass the following parameters:lp
- The pool address for which you want to connect BetExpress.core
- The address of the PrematchCore smart contract on which combo bets will be placed within the deployed contract. Thecore
smart contract must belong to the pool whose address is passed in thelp
parameter.coreType
-"express"
.
As a result, a new BetExpress smart contract will be deployed. You can obtain the address of the deployed BetExpress contract from the
core
parameter emitted by the FactoryNewBet
event. -
To configure your Pool for the deployed BetExpress contract, you need to set the
reinforcementAbility
andminBet
parameters. These parameters determine the maximum share of Pool's liquidity that the BetExpress contract can use and the minimum bet size.To enable your BettingEngine, call the
updateCoreSettings
method in your LP smart contract and pass the following parameters:core
- The address of the deployed BetExpress contract.state
- Set this parameter to1
. If set to2
, your BettingEngine will be disabled, and users will not be able to place bets, although they can still withdraw their rewards.reinforcementAbility
- Set this parameter to the desired maximum share of Pool's liquidity that the BetExpress smart contract can use. The value should be represented as a fraction of 10^12. For example, if you want the BetExpress to use only 10% of the liquidity available in the Pool, set this parameter to100000000000
.minBet
- Set this parameter to the desired minimum bet size.
For other BettingEngines, these parameters determine the maximum share of Pool's liquidity that the deployed BetExpress smart contract can use and the minimum bet size. You can change these settings by calling the
updateCoreSettings
method again.By default, the reinforcement ability for the deployed BettingEngine is 10^12 (100%), and the minimum bet is 1.
To set the
maxReinforcementShare
parameter inside the deployed BetExpress contract, which determines how much of the funds the BetExpress smart contract has at its disposal within the Pool can be lost by a single Condition across all combo bets, follow these steps: -
Call the
changeMaxReinforcementShare
method in the deployed BetExpress smart contract, passing the following parameter:newMaxReinforcementShare
- the desired maximum share where 10^12 is 100% of the currentreinforcementAbility
. You can change these settings in the future.
Note that the Pool owner must perform this action.
-
Grant the BetExpress smart contract address access to the
changeOdds
method of the PrematchCore smart contract, which was passed to theplugExpress
function in step 1. How to grant access depends on how your Pool's Access smart contract is configured.
How To Bet on Combo Bets
To place a combo bet, you must call the bet
function of the LP smart contract and provide the following parameters:
core
- The address of the BetExpress smart contract associated with the LP.amount
- The amount of the bet.expiresAt
- The timestamp after which the bet becomes invalid.betData
- A tuple that includes:affiliate
- The address of the affiliate (frontend) through which you make the combo bet. This address will receive the affiliate's reward for your bet. If you are making a bet independently, you can specify your address to receive affiliate rewards after resolving the condition.data
- The data of the sub-bets included in the combo bet. The data is represented as an ABI-encoded array of tuples(conditionId, outcomeId)
, where:conditionId
- The ID of the condition for the sub-bet.outcomeId
- The ID of the outcome of the condition with IDconditionId
for the sub-bet.
Here is an example of data for placing a combo bet that includes two sub-bets: on outcome 1
of Condition 101
and on outcome 2
of Condition 202
.
core
-"0x7a73EF9087C6E7043ec2125DB159E908B2267B11"
amount
-1000000000000000000
expiresAt
-1684385472
betData
-("0x860d20A09a980C5253a5dBA178E11ec3e1836a22", data)
, wheredata
represents encoded data of the form[ "tuple(uint256 conditionId, uint64 outcomeId)[]", "uint64" ]
.
Potential Issues
Users are unable to place bets due to the TooLargeReinforcement
custom error.
This message indicates that the user is trying to place a combo bet that would cause one of the conditions to exceed the maxReinforcementShare
limit. To increase the limit on how much a single condition can lose across all combo bets, call the changeMaxReinforcementShare
function in the BetExpress smart contract with a new limit (refer to step 3 of the Migration Guide).
Users are unable to place bets due to the AccessNotGranted
custom error.
This means that you still need to grant the BetExpress smart contract access to the changeOdds
function of PrematchCore. The smart contract retrieves information about Conditions from this function (see step 4 of the Migration Guide).
Users cannot place bets because of the TooFewSubbets
custom error.
This means that the user is attempting to place a combo bet with fewer than two sub-bets, which is forbidden.
GraphQL
Motivation
The main goal of the major release is to allow support for combo bets to exist within the same entity as single bets. In the initial version, all single bets were associated with a single Outcome
entity. This needed to be clarified because the Outcome
entity also had the odds
field. GraphQL users might have mistakenly assumed that this field represented the bet odds.
To address this confusion, we are introducing the Selection
entity. It combines the bet, bet odds, and outcome. Now, all bets contain a selections: Selection[]
field that merges single and combo bets into a single entity. For a single bet, this field will contain only one selection entity item, while for a combo bet, it will contain two or more.
Change Log
In order to keep the current implementation, a new version of Subgraph has been added. Use the following endpoints to implement combo bets:
- Polygon: https://thegraph.azuro.org/subgraphs/name/azuro-protocol/azuro-api-polygon-v3 (opens in a new tab)
- Gnosis: https://thegraph.azuro.org/subgraphs/name/azuro-protocol/azuro-api-gnosis-v3 (opens in a new tab)
A new entity called ExpressPrematchRelation has been added. It contains the definition of the pre-match core to which the combo contract is attached.
In addition, the oracles field has been removed from LiquidityPoolContract. Accesses in v2 contracts are granted differently, and this field has become outdated.
Changes to the entities:
AzuroBetContract
- The
oracleGameId
field has been removed. Please usegameId
instead.
Game
-
The following fields have been marked as service fields with
_
prefix:activeConditionsEntityIds
→_activeConditionsEntityIds
resolvedConditionsEntityIds
→_resolvedConditionsEntityIds
canceledConditionsEntityIds
→_canceledConditionsEntityIds
pausedConditionsEntityIds
→_pausedConditionsEntityIds
updatedAt
→_updatedAt
-
The following fields have been renamed:
canceledBlockNumber
→resolvedBlockNumber
canceledBlockTimestamp
→resolvedBlockTimestamp
canceledTxHash
→resolvedTxHash
Condition
-
A new field
wonOutcomeId
is added. -
The
oracleConditionId
field has been removed - useconditionId
instead. -
The following fields have been marked as service fields with
_
prefix:updatedAt
→_updatedAt
-
The following fields have been renamed:
canceledBlockNumber
→resolvedBlockNumber
canceledBlockTimestamp
→resolvedBlockTimestamp
canceledTxHash
→resolvedTxHash
Outcome
- The fields
potentialPayout
,payout
, andbetsAmount
have been removed. - The following fields have been marked as service fields with
_
prefix:betsIds
→_betsEntityIds
- The following fields have been renamed:
rawOdds
should be renamed torawCurrentOdds
.odds
should be replaced withcurrentOdds
. This field contains the current odds for the outcome, and updates with each bet. Do not use this field for bets that have already been place.bets
should be replaced withselections
.
Selection
This entity describes the selected outcome of the game within a single condition. Combo bets contain several selections, while single bets can contain only one selection.
- The fields:
id
- entity id, a composite string{betEntityId}_{conditionId}
bet: Bet!
- a reference to the selection’s bet entityodds
- the odds at which the sub-bet was placedrawOdds
- the odds, raw valueresult
- the result of the sub-bet -Won
orLost
outcome: Outcome!
- the entity of the outcome on which the sub-bet was placed_outcomeId
- id of the outcome on which the sub-bet was made. This is a service field, useoutcome.outcomeId
_oddsDecimals
- the odds decimals
Bet
-
The following fields have been marked as service fields with
_
prefix:tokenDecimals
→_tokenDecimals
oddsDecimals
→_oddsDecimals
isFreebet
→_isFreebet
updatedAt
→_updatedAt
-
The following fields have been renamed:
affilate
→affiliate
-
New fields have been added:
type
- type of bet.Ordinar
for single bet orExpress
for combo betpayout
- actual payout, formatted taking into account decimals. 0 in case of loss, from 1 topotentialPayout
value for a full win or a payout for a cancellation of part of the conditionsapproxSettledAt
- approximate time of bet settlement - time of settlement of the latest condition from all sub-bets + 2 hoursrawPayout
- actual payout, raw valuesettledOdds
- the odds value calculated after all sub-bets was settled. If some of condition of any sub-bet is canceled then this value will be different from theodds
field’s value. In other cases the field will be equal toodds
field. Before all sub-bets are settled the value isnull
.selections: [Selection!]
- theSelection
entity is described above.resolvedBlockNumber
- the block of settlement of the entire bet - the block of settlement of the last condition from all sub-betsresolvedBlockTimestamp
- time of the block of settlement of the entire betresolvedTxHash
- transaction of settlement of the entire betredeemedBlockNumber
- the block of the withdrawPayout transaction (when Bet NFT was redeemed)redeemedBlockTimestamp
- time of the block of the withdrawPayout transaction (when Bet NFT was redeemed)redeemedTxHash
- withdrawPayout transaction hash_conditions
- a list of all conditions from sub-bets_games
- a list of all games from sub-bets_subBetsCount
- total number of sub-bets_wonSubBetsCount
- number of winning sub-bets_lostSubBetsCount
- number of losing sub-bets_canceledSubBetsCount
- number of canceled sub-bets
-
The fields have been removed:
conditionId
- in extreme cases, you can use the service field _conditionIdsoracleConditionId
outcome
- use selectionscondition
- in extreme cases, you can use the service field _conditionsgame
- useselections[].outcome.condition.game
gameStartsAt
- useapproxSettledAt
Freebet
- The following fields have been marked as service fields with
_
prefix:updatedAt
→_updatedAt
Migration Guide
Update the bet
and bets
queries for GraphQL. Refer to the "Change Log" section above to see which fields have been modified.
Example:
query Bets {
bets {
...
odds
conditionId
game {
gameId
}
outcome {
outcomeId
...
}
}
}
query Bets {
bets(where: { type: Ordinar }) {
...
odds
settledOdds
selections {
odds
outcome {
outcomeId
condition {
conditionId
game {
gameId
}
}
...
}
}
}
}
Recommendations:
- To define the type of a bet, use the
type
field from theBet
entity, which can be eitherOrdinary
orExpress
. Do not rely on the count of items in theselections
field.
Deprecation Notice
We strongly recommend not delaying the migration, as Azuro will stop supporting the current version of Subgraph in the next release.
Client
Migration Guide
- Change the GraphQL endpoints.
- Change the GraphQL queries (take a look at the "Migrate Guide" from the section above).
- Update contract ABIs. You can find them here (opens in a new tab).
Bet on Combo Bets
minOdds = '12000000000000'
selections = [
[
'101', // conditionId
'1' // outcomeId
],
[
'202', // conditionId
'2' // outcomeId
]
]
data = ethers.utils.defaultAbiCoder.encode(
[
"tuple(uint256 conditionId, uint64 outcomeId)[]",
"uint64"
],
[
selections,
minOdds
]
)
// Gnosis, XDAI
betNative(coreAddress, deadline, {
affiliate,
data
}, { value: rawAmount })
// Polygon, USDT
bet(coreAddress, rawAmount, deadline, {
affiliate,
data
})