UNPKG

@azuro-org/sdk

Version:

One-stop solution for building betting dApps on the Azuro Protocol.

1,305 lines 146 kB
import {c as cookieKeys,l as localStorageKeys,D as DEFAULT_DEADLINE,L as LIVE_STATISTICS_SUPPORTED_SPORTS,a as LIVE_STATISTICS_SUPPORTED_PROVIDERS}from'./config.js';import {jsx}from'react/jsx-runtime';import {createContext,useContext,useSyncExternalStore,use,useState,useEffect,useRef,useCallback,useMemo,useReducer}from'react';import {chainsData,ConditionsBatchDocument,getAvailableFreebets,getMaxBet,ConditionState,Bet_OrderBy,OrderDirection,GraphBetStatus,BetResult,SelectionResult,BetConditionStatus,GameState,SelectionKind,BetsDocument,GamesDocument,Legacy_Bet_OrderBy,LegacyGameStatus,LegacyBetsDocument,LegacyLiveGamesDocument,ConditionsDocument,GameDocument,groupConditionsByMarket,Game_OrderBy,SportsDocument,SportsNavigationDocument,NavigationDocument,getBetFee,BettorsDocument,ODDS_DECIMALS,GameBetsDocument,ConditionDocument,coreAbi,paymasterAbi,calcMindOdds,getComboBetTypedData,createComboBet,getBetTypedData,createBet,BetState,getBet,getProviderFromId,getPrecalculatedCashouts,getCalculatedCashout,getCashoutTypedData,createCashout,CashoutState,getCashout,getBonuses,getWaveLevels,getWaveStats,getWavePeriods,getWaveLeaderBoard,activateWave}from'@azuro-org/toolkit';import {useAccount,useReadContract,useBalance,useConfig,useSendTransaction,useWaitForTransactionReceipt,useWalletClient,useWriteContract}from'wagmi';import {print,Kind}from'graphql';import {useQuery,useInfiniteQuery,useQueryClient,useMutation}from'@tanstack/react-query';import {getMarketName,getSelectionName}from'@azuro-org/dictionaries';import {formatUnits,parseUnits,decodeEventLog,erc20Abi,encodeFunctionData,maxUint256}from'viem';import {waitForTransactionReceipt}from'wagmi/actions';import {gnosis,base,baseSepolia}from'viem/chains';var SportHub; (function (SportHub) { SportHub["Sports"] = "sports"; SportHub["Esports"] = "esports"; SportHub["Unique"] = "unique"; })(SportHub || (SportHub = {})); var BetType; (function (BetType) { BetType["Unredeemed"] = "unredeemed"; BetType["Accepted"] = "accepted"; BetType["Settled"] = "settled"; BetType["CashedOut"] = "cashedOut"; })(BetType || (BetType = {}));const DumbContext = createContext(undefined); let ready = false; let listeners = []; const readyStore = { setReady(value = true) { ready = value; emitChange(); }, subscribe(listener) { listeners = [...listeners, listener]; return () => { listeners = listeners.filter(l => l !== listener); }; }, getSnapshot() { return ready; }, }; const emitChange = () => { for (let listener of listeners) { listener(); } }; let useAccountWithAA = () => useContext(DumbContext); let _useAAWalletClient = () => useContext(DumbContext); const useExtendedAccount = () => { const account = useAccount(); const ready = useSyncExternalStore(readyStore.subscribe, readyStore.getSnapshot, () => false); const accountWithAA = useAccountWithAA(); return accountWithAA || { ...account, isAAWallet: false, ready }; }; const useAAWalletClient = () => { useSyncExternalStore(readyStore.subscribe, readyStore.getSnapshot, () => false); return _useAAWalletClient(); }; (async () => { try { const pkg = await import('@azuro-org/sdk-social-aa-connector'); if (typeof pkg.useAccount === 'function') { useAccountWithAA = pkg.useAccount; } if (typeof pkg.useAAWalletClient === 'function') { _useAAWalletClient = pkg.useAAWalletClient; } readyStore.setReady(); } catch (e) { readyStore.setReady(); } })();const ChainContext = createContext(null); const useChain = () => { return useContext(ChainContext); }; const useOptionalChain = (chainId) => { let chainData = chainId ? chainsData[chainId] : use(ChainContext); if (!chainData) { throw new Error('Please provide chainId or use ChainProvider'); } return chainData; }; const ChainProvider = (props) => { const { children, initialChainId } = props; const [appChainId, setAppChainId] = useState(initialChainId); const { chain: walletChain, isAAWallet } = useExtendedAccount(); const aaWalletClient = useAAWalletClient(); const walletChainId = walletChain?.id || null; const isRightNetwork = walletChainId === appChainId; const { chain, contracts, betToken, graphql, socket, api, environment } = chainsData[appChainId]; const handleChangeChain = (chainId) => { document.cookie = `${cookieKeys.appChainId}=${chainId};path=/;`; setAppChainId(chainId); }; useEffect(() => { if (isAAWallet && aaWalletClient && aaWalletClient.chain.id !== appChainId) { aaWalletClient.switchChain({ id: appChainId }); } }, [isAAWallet, aaWalletClient, appChainId]); const context = { appChain: chain, chain, walletChain, contracts, betToken, graphql, socket, api, environment, isRightNetwork, setAppChainId: handleChangeChain, }; return (jsx(ChainContext.Provider, { value: context, children: children })); };var SocketCloseReason$1; (function (SocketCloseReason) { SocketCloseReason[SocketCloseReason["Unmount"] = 3000] = "Unmount"; })(SocketCloseReason$1 || (SocketCloseReason$1 = {})); const FeedSocketContext = createContext(null); const useFeedSocket = () => { return useContext(FeedSocketContext); }; const FeedSocketProvider = ({ children }) => { const { socket: socketUrl } = useChain(); const [socket, setSocket] = useState(); const isSocketReady = socket?.readyState === WebSocket.OPEN; const prevSocketUrl = useRef(socketUrl); const isConnectedRef = useRef(false); const connect = () => { if (isConnectedRef.current) { return; } isConnectedRef.current = true; const newSocket = new WebSocket(`${socketUrl}/feed`); const handleOpen = () => { setSocket(newSocket); }; const handleClose = (event) => { setSocket(undefined); isConnectedRef.current = false; newSocket.removeEventListener('open', handleOpen); newSocket.removeEventListener('close', handleClose); newSocket.removeEventListener('error', handleError); if (event.code !== SocketCloseReason$1.Unmount) { setTimeout(connect, 1000); } }; const handleError = () => { isConnectedRef.current = false; }; newSocket.addEventListener('open', handleOpen); newSocket.addEventListener('close', handleClose); newSocket.addEventListener('error', handleError); }; useEffect(() => { connect(); return () => { socket?.close(SocketCloseReason$1.Unmount); }; }, []); useEffect(() => { if (!socket || !isSocketReady || prevSocketUrl.current === socketUrl) { return; } socket.close(); prevSocketUrl.current = socketUrl; }, [socketUrl, isSocketReady]); const value = { socket, isSocketReady }; return (jsx(FeedSocketContext.Provider, { value: value, children: children })); };function debounce(func, wait, withMaxRequests) { let timeout; let requests = 0; return function (...args) { const context = this; const later = function () { timeout = undefined; requests = 0; func.apply(context, args); }; if (withMaxRequests) { requests++; } if (timeout !== undefined) { clearTimeout(timeout); } if (requests > 100) { later(); } else { timeout = setTimeout(later, wait); } }; }const createQueueAction = (subscribe, unsubscribe) => { const actions = { subscribe: [], unsubscribe: [], }; // group batch requests const request = debounce(() => { const subscribeQueue = [...actions.subscribe]; const unsubscribeQueue = [...actions.unsubscribe]; actions.subscribe = []; actions.unsubscribe = []; const weights = {}; subscribeQueue.forEach(id => { if (!weights[id]) { weights[id] = 1; } else { weights[id]++; } }); unsubscribeQueue.forEach(id => { if (!weights[id]) { weights[id] = -1; } else { weights[id]--; } }); const { subscribeWeights, unsubscribeWeights } = Object.keys(weights).reduce((acc, id) => { // ATTN: equal cause we need to fire subscribe for new elements // and then trigger watcher (we don't have store) if (weights[id] > 0) { acc.subscribeWeights[id] = weights[id]; } else if (weights[id] < 0) { acc.unsubscribeWeights[id] = weights[id]; } return acc; }, { subscribeWeights: {}, unsubscribeWeights: {}, }); if (Object.keys(subscribeWeights).length) { subscribe(subscribeWeights); } if (Object.keys(unsubscribeWeights).length) { unsubscribe(unsubscribeWeights); } }, 50); const run = (action, values) => { request(); values.forEach(value => { actions[action].push(value); }); }; return run; };const createWatcher = () => { const timers = new Map(); const subscribers = new Map(); const trigger = (key, value) => { const handlers = subscribers.get(key) || []; handlers.forEach((cb) => { cb(value); }); }; const subscribe = (key, cb) => { const handlers = subscribers.get(key) || []; handlers.push(cb); subscribers.set(key, handlers); return function unsubscribe() { const handlers = subscribers.get(key) || []; const newHandlers = handlers.filter((handler) => handler !== cb); if (newHandlers.length) { subscribers.set(key, newHandlers); } else { subscribers.delete(key); } }; }; const dispatch = (key, value) => { let timer = timers.get(key); if (timer !== undefined) { clearTimeout(timer); } timer = setTimeout(() => { timers.delete(key); trigger(key, value); }, 200); timers.set(key, timer); }; return { subscribe, dispatch, }; };const conditionWatcher = createWatcher();const outcomeWatcher = createWatcher();var Event$1; (function (Event) { Event["Subscribe"] = "SubscribeConditions"; Event["Unsubscribe"] = "UnsubscribeConditions"; Event["Update"] = "ConditionUpdated"; })(Event$1 || (Event$1 = {})); const ConditionUpdatesContext = createContext(null); const useConditionUpdates = () => { return useContext(ConditionUpdatesContext); }; const ConditionUpdatesProvider = ({ children }) => { const { environment } = useChain(); const { socket, isSocketReady } = useFeedSocket(); const subscribers = useRef({}); const subscribe = useCallback((weights) => { if (socket?.readyState !== 1) { return; } const newSubscribers = []; Object.keys(weights).forEach((conditionId) => { if (typeof subscribers.current[conditionId] === 'undefined') { newSubscribers.push(conditionId); subscribers.current[conditionId] = 0; } subscribers.current[conditionId] += weights[conditionId]; }); if (!newSubscribers.length) { return; } socket.send(JSON.stringify({ event: Event$1.Subscribe, data: { conditionIds: Object.keys(weights), environment, }, })); }, [socket, environment]); const unsubscribeCall = useCallback((conditionIds) => { if (socket?.readyState !== 1) { return; } socket.send(JSON.stringify({ event: Event$1.Unsubscribe, data: { conditionIds, environment, }, })); }, [socket, environment]); const unsubscribe = useCallback((weights) => { if (socket?.readyState !== 1) { return; } // we mustn't unsubscribe for condition if it has more that 1 subscriber const newUnsubscribers = []; Object.keys(weights).forEach((conditionId) => { if (subscribers.current[conditionId]) { subscribers.current[conditionId] += weights[conditionId]; if (subscribers.current[conditionId] === 0) { delete subscribers.current[conditionId]; newUnsubscribers.push(conditionId); } } }); if (!newUnsubscribers.length) { return; } unsubscribeCall(newUnsubscribers); }, [socket, unsubscribeCall]); const runAction = useCallback(createQueueAction(subscribe, unsubscribe), [subscribe, unsubscribe]); const subscribeToUpdates = useCallback((conditionIds) => { runAction('subscribe', conditionIds); }, [runAction]); const unsubscribeToUpdates = useCallback((conditionIds) => { runAction('unsubscribe', conditionIds); }, [runAction]); useEffect(() => { if (!isSocketReady || !socket) { return; } const handleMessage = (message) => { const { event, data } = JSON.parse(message.data); if (event !== Event$1.Update) { return; } const { id: conditionId, outcomes, state } = data; const eventData = { conditionId: conditionId, state, }; conditionWatcher.dispatch(conditionId, eventData); outcomes.forEach(({ outcomeId, currentOdds }) => { outcomeWatcher.dispatch(`${conditionId}-${outcomeId}`, { odds: +currentOdds, }); }); }; const handleClose = () => { subscribers.current = {}; }; socket.addEventListener('message', handleMessage); socket.addEventListener('close', handleClose); return () => { socket.removeEventListener('message', handleMessage); socket.removeEventListener('close', handleClose); if (socket.readyState !== WebSocket.OPEN) { subscribers.current = {}; } }; }, [socket]); const value = { isSocketReady, subscribeToUpdates, unsubscribeToUpdates, }; return (jsx(ConditionUpdatesContext.Provider, { value: value, children: children })); };const liveStatisticWatcher = createWatcher();var SocketCloseReason; (function (SocketCloseReason) { SocketCloseReason[SocketCloseReason["Unmount"] = 3000] = "Unmount"; })(SocketCloseReason || (SocketCloseReason = {})); var Status; (function (Status) { Status["InProgress"] = "In progress"; Status["NotStarted"] = "Not started yet"; Status["Finished"] = "Finished"; Status["PreFinished"] = "PreFinished"; Status["CoverageLost"] = "Coverage lost"; Status["Suspended"] = "Suspended"; })(Status || (Status = {})); var SoccerIncidentType; (function (SoccerIncidentType) { SoccerIncidentType["Goal"] = "goal"; SoccerIncidentType["Corner"] = "corner"; SoccerIncidentType["YellowCard"] = "yellow_card"; SoccerIncidentType["RedCard"] = "red_card"; SoccerIncidentType["Substitution"] = "substitution"; SoccerIncidentType["GoalKick"] = "goal_kick"; SoccerIncidentType["TrownIn"] = "throw_in"; SoccerIncidentType["Penalty"] = "penalty"; SoccerIncidentType["Offside"] = "offside"; SoccerIncidentType["ScoreAfterFirstHalf"] = "score_after_first_half"; SoccerIncidentType["ScoreAfterFullTime"] = "score_after_full_time"; SoccerIncidentType["ScoreAfterExtraTime"] = "score_after_extra_time"; SoccerIncidentType["ScoreAfterExtraTimeHalfTime"] = "score_after_extra_time_half_time"; SoccerIncidentType["ScoreAfterPenalties"] = "score_after_penalties"; SoccerIncidentType["ExtraTimeStart"] = "extra_time_start"; SoccerIncidentType["PenaltiesStart"] = "penalties_start"; SoccerIncidentType["PenaltyShootOut"] = "penalty_shoot_out"; SoccerIncidentType["MissPenalty"] = "miss_penalty"; })(SoccerIncidentType || (SoccerIncidentType = {})); var BasketballIncidentType; (function (BasketballIncidentType) { BasketballIncidentType["MatchStart"] = "match_start"; BasketballIncidentType["MatchEnd"] = "match_end"; BasketballIncidentType["QuarterStart"] = "quarter_start"; BasketballIncidentType["QuarterEnd"] = "quarter_end"; BasketballIncidentType["Point"] = "point"; BasketballIncidentType["FreeThrow"] = "free_throw"; BasketballIncidentType["MissedThrow"] = "missed_throw"; BasketballIncidentType["MissedFreeThrow"] = "missed_free_throw"; BasketballIncidentType["PlayersOnCort"] = "players_on_cort"; BasketballIncidentType["PlayersWarmingUp"] = "players_warming_up"; BasketballIncidentType["Foul"] = "foul"; BasketballIncidentType["Rebound"] = "rebound"; BasketballIncidentType["Timeout"] = "timeout"; BasketballIncidentType["TimeoutStart"] = "timeout_start"; BasketballIncidentType["TimeoutEnd"] = "timeout_end"; BasketballIncidentType["Turnover"] = "turnover"; BasketballIncidentType["Block"] = "block"; BasketballIncidentType["Steal"] = "steal"; BasketballIncidentType["Halftime"] = "halftime"; BasketballIncidentType["Fulltime"] = "fulltime"; BasketballIncidentType["Overtime"] = "overtime"; BasketballIncidentType["OvertimeStart"] = "overtime_start"; BasketballIncidentType["OvertimeEnd"] = "overtime_end"; })(BasketballIncidentType || (BasketballIncidentType = {})); var TennisIncidentType; (function (TennisIncidentType) { TennisIncidentType["MatchStart"] = "match_start"; TennisIncidentType["MatchEnd"] = "match_end"; TennisIncidentType["SetStart"] = "set_start"; TennisIncidentType["SetEnd"] = "set_end"; TennisIncidentType["PlayersOnCort"] = "players_on_cort"; TennisIncidentType["PlayersWarmingUp"] = "players_warming_up"; TennisIncidentType["FirstServer"] = "first_server"; TennisIncidentType["Service"] = "service"; TennisIncidentType["Point"] = "point"; TennisIncidentType["Game"] = "game"; TennisIncidentType["Set"] = "set"; TennisIncidentType["Fault"] = "fault"; })(TennisIncidentType || (TennisIncidentType = {})); var VolleyballIncidentType; (function (VolleyballIncidentType) { VolleyballIncidentType["MatchStart"] = "match_start"; VolleyballIncidentType["MatchEnd"] = "match_end"; VolleyballIncidentType["SetStart"] = "set_start"; VolleyballIncidentType["SetEnd"] = "set_end"; VolleyballIncidentType["FirstServer"] = "first_server"; VolleyballIncidentType["Rally"] = "rally"; VolleyballIncidentType["PointWon"] = "point_won"; VolleyballIncidentType["ServiceError"] = "service_error"; VolleyballIncidentType["Timeout"] = "timeout"; VolleyballIncidentType["TimeoutStart"] = "timeout_start"; VolleyballIncidentType["TimeoutEnd"] = "timeout_end"; VolleyballIncidentType["Ace"] = "ace"; VolleyballIncidentType["MatchDelay"] = "match_delay"; VolleyballIncidentType["PlayersOnCort"] = "players_on_cort"; VolleyballIncidentType["PlayersWarmingUp"] = "players_warming_up"; VolleyballIncidentType["Service"] = "service"; })(VolleyballIncidentType || (VolleyballIncidentType = {})); const LiveStatisticsSocketContext = createContext(null); const useLiveStatisticsSocket = () => { return useContext(LiveStatisticsSocketContext); }; const LiveStatisticsSocketProvider = ({ children }) => { const { socket: socketUrl } = useChain(); const [socket, setSocket] = useState(); const isSocketReady = socket?.readyState === WebSocket.OPEN; const isConnectedRef = useRef(false); const prevSocketUrl = useRef(socketUrl); const subscribers = useRef({}); const subscribe = useCallback((weights) => { if (socket?.readyState !== 1) { return; } Object.keys(weights).forEach((gameId) => { if (typeof subscribers.current[gameId] === 'undefined') { subscribers.current[gameId] = 0; } subscribers.current[gameId] += weights[gameId]; }); socket.send(JSON.stringify({ action: 'subscribe', gameIds: Object.keys(weights), })); }, [socket]); const unsubscribeCall = (gameIds) => { if (socket?.readyState !== 1) { return; } socket.send(JSON.stringify({ action: 'unsubscribe', gameIds, })); }; const unsubscribe = useCallback((weights) => { if (socket?.readyState !== 1) { return; } // we mustn't unsubscribe for condition if it has more that 1 subscriber const newUnsubscribers = []; Object.keys(weights).forEach((gameId) => { if (subscribers.current[gameId]) { subscribers.current[gameId] += weights[gameId]; if (subscribers.current[gameId] === 0) { delete subscribers.current[gameId]; newUnsubscribers.push(gameId); } } }); if (!newUnsubscribers.length) { return; } unsubscribeCall(newUnsubscribers); }, [socket, unsubscribeCall]); const runAction = useCallback(createQueueAction(subscribe, unsubscribe), [subscribe, unsubscribe]); const subscribeToUpdates = useCallback((gameIds) => { runAction('subscribe', gameIds); }, [runAction]); const unsubscribeToUpdates = useCallback((gameIds) => { runAction('unsubscribe', gameIds); }, [runAction]); const connect = () => { if (isConnectedRef.current) { return; } isConnectedRef.current = true; const newSocket = new WebSocket(`${socketUrl}/statistics/games`); const handleOpen = () => { setSocket(newSocket); }; const handleClose = (event) => { setSocket(undefined); isConnectedRef.current = false; newSocket.removeEventListener('open', handleOpen); newSocket.removeEventListener('message', handleMessage); newSocket.removeEventListener('close', handleClose); newSocket.removeEventListener('error', handleError); if (event.code !== SocketCloseReason.Unmount) { setTimeout(connect, 1000); } }; const handleError = () => { isConnectedRef.current = false; }; const handleMessage = (message) => { JSON.parse(message.data.toString()).forEach((data) => { const { id, fixture, live } = data; let statsData = { status: fixture?.status || null, scoreBoard: live?.scoreBoard || null, stats: live?.stats || null, virtualCourtId: fixture?.virtualCourtId || null, timeline: live?.timeline || null, jerseyColors: live?.jerseyColors || null, clock: live?.clock || null, }; liveStatisticWatcher.dispatch(id, statsData); }); }; newSocket.addEventListener('open', handleOpen); newSocket.addEventListener('message', handleMessage); newSocket.addEventListener('close', handleClose); newSocket.addEventListener('error', handleError); }; useEffect(() => { connect(); return () => { socket?.close(); }; }, []); useEffect(() => { if (!socket || !isSocketReady || prevSocketUrl.current === socketUrl) { return; } socket.close(); prevSocketUrl.current = socketUrl; }, [socketUrl, isSocketReady]); const value = { isSocketReady, subscribeToUpdates, unsubscribeToUpdates, }; return (jsx(LiveStatisticsSocketContext.Provider, { value: value, children: children })); };const createBatch = (fn, isSet = true) => { let idsWaitList = isSet ? new Set() : []; let resolversWaitList = []; const request = debounce(async (fn, ...rest) => { const ids = [...idsWaitList]; const resolvers = resolversWaitList; if (idsWaitList instanceof Set) { idsWaitList.clear(); } else { idsWaitList = []; } resolversWaitList = []; try { const data = await fn(ids, ...rest); resolvers.forEach((resolve) => { resolve(data); }); } catch (err) { resolvers.forEach((resolve) => { resolve(undefined); }); } }, 50, true); const batch = (ids, ...rest) => { request(fn, ...rest); ids.forEach(id => { if (idsWaitList instanceof Set) { idsWaitList.add(id); } else { idsWaitList.push(id); } }); return new Promise((resolve) => { resolversWaitList.push(resolve); }); }; return batch; };const extractOperationName = (document) => { let operationName = undefined; const defs = document.definitions.filter((definition) => (typeof definition === 'object' && definition !== null && 'kind' in definition && definition.kind === Kind.OPERATION_DEFINITION)); if (defs.length === 1) { operationName = defs[0].name?.value; } return operationName; }; const gqlRequest = async (props) => { const { url, document, variables } = props; const opName = extractOperationName(document); const params = new URLSearchParams(); if (opName) { params.append('op', opName); } if (variables && typeof variables === 'object' && 'limit' in variables) { params.append('limit', String(variables.limit)); } if (variables && typeof variables === 'object' && 'offset' in variables) { params.append('offset', String(variables.offset)); } const response = await fetch(`${url}?${params}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/graphql-response+json', }, body: JSON.stringify({ query: print(document), variables, }), cache: 'no-cache', }); if (!response.ok) { throw new Error('Network response was not ok'); } const { data } = await response.json(); return data; };const getConditions = async (conditionEntityIds, gqlLink) => { const { conditions } = await gqlRequest({ url: gqlLink, document: ConditionsBatchDocument, variables: { conditionFilter: { id_in: conditionEntityIds, }, }, }); return conditions.reduce((acc, condition) => { const { conditionId, outcomes: _outcomes } = condition; const outcomes = _outcomes.reduce((acc, outcome) => { acc[outcome.outcomeId] = outcome; return acc; }, {}); acc[conditionId] = { ...condition, outcomes, }; return acc; }, {}); }; const batchFetchConditions = createBatch(getConditions);const formatToFixed = (value, digitsCount) => { value = String(value); if (!/\./.test(value)) { return value; } const [int, digits] = value.split('.'); if (digitsCount === 0) { return int; } return `${int}.${digits.substring(0, digitsCount)}`; };const useOdds = ({ selections }) => { const { graphql } = useChain(); const { isSocketReady, subscribeToUpdates, unsubscribeToUpdates } = useConditionUpdates(); const [odds, setOdds] = useState({}); const [isFetching, setFetching] = useState(true); const { selectionsKey, conditionsKey } = useMemo(() => (selections?.reduce((acc, { conditionId, outcomeId }) => { acc.selectionsKey += acc.selectionsKey ? `-${conditionId}/${outcomeId}` : `${conditionId}/${outcomeId}`; acc.conditionsKey += acc.conditionsKey ? `-${conditionId}` : `${conditionId}`; return acc; }, { selectionsKey: '', conditionsKey: '', })), [selections]); const prevSelectionsKeyRef = useRef(selectionsKey); if (selectionsKey !== prevSelectionsKeyRef.current) { setFetching(true); setOdds({}); } prevSelectionsKeyRef.current = selectionsKey; const totalOdds = useMemo(() => { return +formatToFixed(Object.keys(odds).reduce((acc, key) => acc * +odds[key], 1), 5); }, [odds]); useEffect(() => { if (!isSocketReady) { return; } const conditionIds = conditionsKey.split('-'); subscribeToUpdates(conditionIds); return () => { unsubscribeToUpdates(conditionIds); }; }, [isSocketReady, conditionsKey]); useEffect(() => { const unsubscribeList = selections.map(({ conditionId, outcomeId }) => { return outcomeWatcher.subscribe(`${conditionId}-${outcomeId}`, (data) => { setOdds(prevOdds => ({ ...prevOdds, [`${conditionId}-${outcomeId}`]: data.odds, })); }); }); return () => { unsubscribeList.forEach((unsubscribe) => { unsubscribe(); }); }; }, [selectionsKey]); useEffect(() => { const conditionIds = conditionsKey?.split('-'); if (!conditionIds.length) { return; } (async () => { const data = await batchFetchConditions(conditionIds, graphql.feed); const newOdds = selections.reduce((acc, { conditionId, outcomeId }) => { acc[`${conditionId}-${outcomeId}`] = +(data[conditionId]?.outcomes[outcomeId]?.odds || 1); return acc; }, {}); setOdds(newOdds); setFetching(false); })(); }, [selectionsKey, graphql.feed]); return { data: { odds, totalOdds, }, isFetching, }; };const useConditionsState = ({ conditionIds, initialStates }) => { const { graphql } = useChain(); const { isSocketReady, subscribeToUpdates, unsubscribeToUpdates } = useConditionUpdates(); const [states, setStates] = useState(initialStates ?? {}); const [isStatesFetching, setStatesFetching] = useState(!initialStates); const conditionsKey = useMemo(() => conditionIds.join('-'), [conditionIds]); const prevConditionsKeyRef = useRef(conditionsKey); if (conditionIds.length && conditionsKey !== prevConditionsKeyRef.current) { setStatesFetching(true); setStates({}); } prevConditionsKeyRef.current = conditionsKey; useEffect(() => { if (!isSocketReady || !conditionsKey.length) { return; } subscribeToUpdates(conditionIds); return () => { unsubscribeToUpdates(conditionIds); }; }, [conditionsKey, isSocketReady]); useEffect(() => { if (!conditionIds.length) { return; } const unsubscribeList = conditionIds.map((conditionId) => { return conditionWatcher.subscribe(conditionId, (data) => { const { state: newState } = data; setStates(states => ({ ...states, [conditionId]: newState, })); }); }); return () => { unsubscribeList.forEach((unsubscribe) => { unsubscribe(); }); }; }, [conditionsKey]); useEffect(() => { if (!conditionIds.length || initialStates) { return; } (async () => { const data = await batchFetchConditions(conditionIds, graphql.feed); const newStatuses = (conditionIds || []).reduce((acc, conditionId) => { if (conditionId && data?.[conditionId]) { acc[conditionId] = data[conditionId].state; } return acc; }, {}); setStatesFetching(false); setStates(newStatuses); })(); }, [conditionsKey]); return { data: states, isFetching: isStatesFetching, }; };const useAvailableFreebets = (props) => { const { account, affiliate, selections, chainId, query = {} } = props; const { chain: appChain } = useOptionalChain(chainId); const selectionsKey = useMemo(() => (selections.map(({ conditionId, outcomeId }) => `${conditionId}/${outcomeId}`).join('-')), [selections]); return useQuery({ queryKey: ['available-freebets', appChain.id, account?.toLowerCase(), affiliate?.toLowerCase(), selectionsKey], queryFn: async () => { const freebets = await getAvailableFreebets({ chainId: appChain.id, account, affiliate, selections, }); if (!freebets) { return []; } return freebets; }, refetchOnWindowFocus: false, ...query, }); };const useForceUpdate = () => { const [increment, setState] = useState(0); const forceUpdate = useCallback(() => { setState((v) => ++v); }, []); return { increment, forceUpdate }; };const useMaxBet = (props) => { const { selections, chainId, query = {} } = props; const { chain: appChain } = useOptionalChain(chainId); const { isSocketReady, subscribeToUpdates, unsubscribeToUpdates } = useConditionUpdates(); const selectionsKey = useMemo(() => (selections.map(({ conditionId, outcomeId }) => `${conditionId}/${outcomeId}`).join('-')), [selections]); const queryData = useQuery({ queryKey: ['max-bet', appChain.id, selectionsKey], queryFn: async () => { const data = await getMaxBet({ chainId: appChain.id, selections, }); if (!data) { return '0'; } return formatToFixed(data.maxBet, 3); }, gcTime: 0, // disable cache refetchOnWindowFocus: false, enabled: Boolean(selections.length), ...query, }); const { refetch } = queryData; useEffect(() => { if (!isSocketReady || !selections?.length) { return; } const conditionIds = selections.map(({ conditionId }) => conditionId); subscribeToUpdates(conditionIds); return () => { unsubscribeToUpdates(conditionIds); }; }, [selectionsKey, isSocketReady]); useEffect(() => { if (!selections?.length) { return; } const unsubscribeList = selections.map(({ conditionId }) => { return conditionWatcher.subscribe(conditionId, () => refetch()); }); return () => { unsubscribeList.forEach((unsubscribe) => { unsubscribe(); }); }; }, [selectionsKey]); return queryData; };var BetslipDisableReason; (function (BetslipDisableReason) { BetslipDisableReason["ConditionState"] = "ConditionState"; BetslipDisableReason["BetAmountGreaterThanMaxBet"] = "BetAmountGreaterThanMaxBet"; BetslipDisableReason["BetAmountLowerThanMinBet"] = "BetAmountLowerThanMinBet"; BetslipDisableReason["ComboWithForbiddenItem"] = "ComboWithForbiddenItem"; BetslipDisableReason["ComboWithSameGame"] = "ComboWithSameGame"; BetslipDisableReason["SelectedOutcomesTemporarySuspended"] = "SelectedOutcomesTemporarySuspended"; /** * @deprecated Only for v2 */ BetslipDisableReason["PrematchConditionInStartedGame"] = "PrematchConditionInStartedGame"; BetslipDisableReason["FreeBetExpired"] = "FreeBetExpired"; })(BetslipDisableReason || (BetslipDisableReason = {})); const BaseBetslipContext = createContext(null); const DetailedBetslipContext = createContext(null); const useBaseBetslip = () => { return useContext(BaseBetslipContext); }; const useDetailedBetslip = () => { return useContext(DetailedBetslipContext); }; const BetslipProvider = (props) => { const { children, affiliate } = props; const { appChain } = useChain(); const account = useExtendedAccount(); const { forceUpdate } = useForceUpdate(); const [items, setItems] = useState([]); const [selectedFreebet, setFreebet] = useState(); const [betAmount, setBetAmount] = useState(''); // const [ batchBetAmounts, setBatchBetAmounts ] = useState<Record<string, string>>({}) // const [ isBatch, setBatch ] = useState(false) const { data: freebets, isFetching: isFreebetsFetching } = useAvailableFreebets({ account: account?.address, affiliate: affiliate, selections: items, query: { enabled: Boolean(affiliate) && Boolean(account?.address) && Boolean(items.length), }, }); const { data: oddsData, isFetching: isOddsFetching } = useOdds({ // betAmount, // batchBetAmounts, selections: items, }); const { data: states, isFetching: isStatesFetching } = useConditionsState({ conditionIds: items.map(({ conditionId }) => conditionId), }); const { data: maxBet, isFetching: isMaxBetFetching } = useMaxBet({ selections: items }); const { odds, totalOdds } = oddsData; const isCombo = items.length > 1; const checkDifferentGames = (items) => { const gameIds = items.map(({ gameId }) => gameId); return gameIds.length === new Set(gameIds).size; }; // const createInitialBatchAmounts = (items: AzuroSDK.BetslipItem[]) => { // setBatchBetAmounts(batchAmounts => { // const newBatchAmounts: Record<string, string> = {} // items.forEach(({ conditionId, outcomeId }) => { // const key = `${conditionId}-${outcomeId}` // newBatchAmounts[key] = batchAmounts[key] || '' // }) // return newBatchAmounts // }) // } const totalBetAmount = useMemo(() => { // if (isBatch) { // return String(Object.values(batchBetAmounts).reduce((acc, amount) => acc + +amount, 0)) // } if (selectedFreebet) { return selectedFreebet.amount; } return betAmount; }, // [ isBatch, betAmount, batchBetAmounts, selectedFreeBet ] [betAmount, selectedFreebet]); const isConditionsInActiveState = useMemo(() => { return Object.values(states).every(state => state === ConditionState.Active); }, [states]); const isComboWithDifferentGames = useMemo(() => { return !isCombo || checkDifferentGames(items); }, [isCombo, items]); const isFreeBetAllowed = useMemo(() => { if (!selectedFreebet) { return true; } return selectedFreebet.expiresAt > Date.now(); }, [selectedFreebet]); const isComboAllowed = useMemo(() => { return !isCombo || isComboWithDifferentGames && items.every(({ isExpressForbidden }) => !isExpressForbidden); }, [isCombo, items]); // const minBet = isLiveBet && !appChain?.testnet ? MIN_LIVE_BET_AMOUNT : undefined const minBet = undefined; // TODO const isMaxBetBiggerThanZero = typeof maxBet !== 'undefined' ? +maxBet > 0 : true; const isAmountLowerThanMaxBet = Boolean(betAmount) && typeof maxBet !== 'undefined' ? +betAmount <= +maxBet : true; const isAmountBiggerThanMinBet = true; const isBetAllowed = (isConditionsInActiveState && isComboAllowed && isFreeBetAllowed && isMaxBetBiggerThanZero && isAmountLowerThanMaxBet && isAmountBiggerThanMinBet); let disableReason = (() => { if (!isConditionsInActiveState) { return BetslipDisableReason.ConditionState; } if (!isFreeBetAllowed) { return BetslipDisableReason.FreeBetExpired; } if (!isComboAllowed) { if (!isComboWithDifferentGames) { return BetslipDisableReason.ComboWithSameGame; } else { return BetslipDisableReason.ComboWithForbiddenItem; } } if (!isMaxBetBiggerThanZero) { return BetslipDisableReason.SelectedOutcomesTemporarySuspended; } if (!isAmountLowerThanMaxBet) { return BetslipDisableReason.BetAmountGreaterThanMaxBet; } })(); // const changeBatch = useCallback((value: boolean) => { // setBatch(value) // if (value) { // setBetAmount('') // createInitialBatchAmounts(items) // } // else { // setBatchBetAmounts({}) // } // }, [ items ]) const changeBetAmount = useCallback((value) => { setBetAmount(value); }, []); // const changeBatchBetAmount = useCallback((item: ChangeBatchBetAmountItem, value: string) => { // const { conditionId, outcomeId } = item // const key = `${conditionId}-${outcomeId}` // const decimals = ([ base.id, baseSepolia.id ] as number[]).includes(appChain.id) ? 4 : 2 // setBatchBetAmounts(amounts => { // return { // ...amounts, // [key]: formatToFixed(value, decimals), // } // }) // }, [ appChain ]) const addItem = useCallback((item) => { setItems(items => { let newItems; const replaceIndex = items.findIndex(({ gameId }) => gameId === item.gameId); // if cart contains outcome from same game as new item // then replace old item if (replaceIndex !== -1) { // if (isBatchBetWithSameGameEnabled) { // const { conditionId, outcomeId } = items[replaceIndex]! // // if it's exactly the same outcome, don't change the state // if (conditionId === item.conditionId && outcomeId === item.outcomeId) { // return items // } // newItems = [ ...items, item ] // setBatch(true) // createInitialBatchAmounts(newItems) // } // else { newItems = [...items]; newItems[replaceIndex] = item; // } } else { newItems = [...items, item]; } localStorage.setItem(localStorageKeys.betslipItems, JSON.stringify(newItems)); // if (isBatch) { // createInitialBatchAmounts(newItems) // } return newItems; }); }, // [ isBatch ] []); const removeItem = useCallback((itemProps) => { const { conditionId, outcomeId } = itemProps; setItems(items => { const newItems = items.filter((item) => !(item.conditionId === conditionId && item.outcomeId === outcomeId)); // if (newItems.length < 2) { // setBatch(false) // } // setBatchBetAmounts(batchAmounts => { // if (newItems.length < 2) { // const lastItem = newItems[0] // if (lastItem) { // const { conditionId, outcomeId } = lastItem // const amount = batchAmounts[`${conditionId}-${outcomeId}`] // if (amount) { // setBetAmount(amount) // } // } // return {} // } // const newBatchAmounts = { ...batchAmounts } // delete newBatchAmounts[`${conditionId}-${outcomeId}`] // return newBatchAmounts // }) localStorage.setItem(localStorageKeys.betslipItems, JSON.stringify(newItems)); return newItems; }); }, []); const clear = useCallback(() => { setItems([]); // setBatch(false) // setBatchBetAmounts({}) setBetAmount(''); setFreebet(undefined); localStorage.setItem(localStorageKeys.betslipItems, JSON.stringify([])); }, []); const prevChainId = useRef(appChain.id); useEffect(() => { if (prevChainId.current !== appChain.id) { clear(); prevChainId.current = appChain.id; } }, [appChain.id]); useEffect(() => { let storedItems = JSON.parse(localStorage.getItem(localStorageKeys.betslipItems) || '[]'); if (!Array.isArray(storedItems)) { return; } // const isDifferentGames = checkDifferentGames(storedItems) // if (!isDifferentGames) { // setBatch(true) // createInitialBatchAmounts(storedItems) // } setItems(storedItems); }, []); useEffect(() => { if (!selectedFreebet || selectedFreebet.expiresAt <= Date.now()) { return; } const timeout = setTimeout(() => { forceUpdate(); }, selectedFreebet.expiresAt - Date.now()); return () => { clearTimeout(timeout); }; }, [selectedFreebet]); const baseValue = useMemo(() => ({ items, addItem, removeItem, clear, }), [ items, addItem, removeItem, clear, ]); const detailedValue = useMemo(() => ({ betAmount: totalBetAmount, // batchBetAmounts, odds, totalOdds, maxBet: +(maxBet || 0), minBet, selectedFreebet, freebets, states, disableReason, changeBetAmount, // changeBatchBetAmount, // changeBatch, selectFreebet: setFreebet, // isBatch, isStatesFetching, isOddsFetching, isFreebetsFetching, isMaxBetFetching, isBetAllowed, }), [ totalBetAmount, // batchBetAmounts, odds, totalOdds, maxBet, minBet, selectedFreebet, freebets, states, disableReason, changeBetAmount, // changeBatchBetAmount, // changeBatch, setFreebet, // isBatch, isStatesFetching, isOddsFetching, isFreebetsFetching, isMaxBetFetching, isBetAllowed, ]); return (jsx(BaseBetslipContext.Provider, { value: baseValue, children: jsx(DetailedBetslipContext.Provider, { value: detailedValue, children: children }) })); };const gameWathcer = createWatcher();var Event; (function (Event) { Event["Subscribe"] = "SubscribeGames"; Event["Unsubscribe"] = "UnsubscribeGames"; Event["Update"] = "GameUpdated"; })(Event || (Event = {})); const GameUpdatesContext = createContext(null); const useGameUpdates = () => { return useContext(GameUpdatesContext); }; const GameUpdatesProvider = ({ children }) => { const { environment } = useChain(); const { socket, isSocketReady } = useFeedSocket(); const subscribers = useRef({}); const subscribe = useCallback((weights) => { if (socket?.readyState !== 1) { return; } const newSubscribers = []; Object.keys(weights).forEach((conditionId) => { if (typeof subscribers.current[conditionId] === 'undefined') { newSubscribers.push(conditionId); subscribers.current[conditionId] = 0; } subscribers.current[conditionId] += weights[conditionId]; }); if (!newSubscribers.length) { return; } socket.send(JSON.stringify({ event: Event.Subscribe, data: { gameIds: Object.keys(weights), environment, }, })); }, [socket, environment]); const unsubscribeCall = useCallback((gameIds) => { if (socket?.readyState !== 1) { return; } socket.send(JSON.stringify({ event: Event.Unsubscribe, data: { gameIds, environment, }, })); }, [socket, environment]); const unsubscribe = useCallback((weights) => { if (socket?.readyState !== 1) { return; } // we mustn't unsubscribe for condition if it has more that 1 subscriber const newUnsubscribers = []; Object.keys(weights).forEach((gameId) => { if (subscribers.current[gameId]) { subscribers.current[gameId] += weights[gameId]; if (subscribers.current[gameId] === 0) { delete subscribers.current[gameId]; newUnsubscribers.push(gameId); } } }); if (!newUnsubscribers.length) { return; } unsubscribeCall(newUnsubscribers); }, [socket, unsubscribeCall]); const runAction = useCallback(createQueueAction(subscribe, unsubscribe), [subscribe, unsubscribe]); const subscribeToUpdates = useCallback((gameIds) => { runAction('subscribe', gameIds); }, [runAction]); const unsubscribeToUpdates = useCallback((gameIds) => { runAction('