@azuro-org/sdk
Version:
One-stop solution for building betting dApps on the Azuro Protocol.
1,305 lines • 146 kB
JavaScript
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('