Adding Live. Step 3: Realtime market updates
Add WebSocket
As for prematch games, live game's markets are constantly updated:
- Market can be stopped and unstopped.
- Outcomes odds can be changed.
To receive updates, utilize the Socket API. Begin by creating a new socket instance.
The simplest approach is to utilize or duplicate the SocketContext
from the SDK:
https://github.com/Azuro-protocol/sdk/blob/v3.1.0/src/contexts/socket.tsx (opens in a new tab)
The main components here are the event-emitters:
oddsWatcher
(source code (opens in a new tab))conditionStatusWatcher
(source code (opens in a new tab))
These events are triggered by the prematch contract event listeners (opens in a new tab) and the socket message handler:
socket.current.onmessage = (message: MessageEvent<SocketData>) => {
JSON.parse(message.data.toString()).forEach((data: SocketData[0]) => {
const { id: conditionId, reinforcement, margin, winningOutcomesCount } = data
if (data.outcomes) {
const eventData: OddsChangedData = {
conditionId: conditionId,
reinforcement: +reinforcement,
margin: +margin,
winningOutcomesCount: +winningOutcomesCount,
outcomes: {},
}
eventData.outcomes = data.outcomes.reduce((acc, { id, odds, clearOdds, maxStake }) => {
acc[id] = {
odds,
clearOdds,
maxBet: maxStake,
}
return acc
}, {} as Record<number, OddsChangedData['outcomes'][0]>)
oddsWatcher.dispatch(conditionId, eventData)
}
if (data.state) {
conditionStatusWatcher.dispatch(conditionId, data.state)
}
})
}
Use Created WebSocket Context
You can subscribe to updates by providing condition IDs to listen to.
Here's how it's implemented in the SDK:
- useSelection (opens in a new tab): Used in outcome buttons within the market selection UI, including event lists and details pages.
- useStatuses (opens in a new tab): Utilized in the betslip for user selected outcomes.
- useOdds (opens in a new tab): Employed in the betslip for user selected outcomes.
part of useSelection.ts
import { isLiveEntity } from '@/config'
export const useSelection = ({ selection, initialOdds, initialStatus }: Props) => {
const { coreAddress, conditionId, outcomeId } = selection
const { contracts } = useChain()
const { prematchClient } = useApolloClients()
const { isSocketReady, subscribeToUpdates, unsubscribeToUpdates } = useSocket()
const isLive = isLiveEntity(selection)
useEffect(() => {
if (!isLive || !isSocketReady) {
return
}
subscribeToUpdates([ conditionId ])
return () => {
unsubscribeToUpdates([ conditionId ])
}
}, [ isSocketReady ])
useEffect(() => {
const unsubscribe = oddsWatcher.subscribe(`${conditionId}`, `${outcomeId}`, async (oddsData?: OddsChangedData) => {
let odds: string | number | undefined = oddsData?.outcomes?.[String(outcomeId)]?.odds
if (!odds) {
const rawOdds = await publicClient!.readContract({
address: contracts.prematchCore.address,
abi: contracts.prematchCore.abi,
functionName: 'calcOdds',
args: [
BigInt(conditionId),
BigInt(1),
BigInt(outcomeId),
],
})
odds = formatUnits(rawOdds, ODDS_DECIMALS)
}
else {
setOddsFetching(false)
}
setOdds(+odds)
})
return () => {
unsubscribe()
}
}, [ publicClient ])
useEffect(() => {
const unsubscribe = conditionStatusWatcher.subscribe(`${conditionId}`, (newStatus: ConditionStatus) => {
setStatusFetching(false)
setStatus(newStatus)
})
return () => {
unsubscribe()
}
}, [])
/* ... */
/* See full code https://github.com/Azuro-protocol/sdk/blob/v3.1.0/src/hooks/useSelection.ts */