diff --git a/.env.qa b/.env.qa index b496f08..9358379 100644 --- a/.env.qa +++ b/.env.qa @@ -13,4 +13,4 @@ VITE_FIREBASE_DB_URL=https://inspired-bebop-399607-default-rtdb.firebaseio.com VITE_FIREBASE_PROJECT_ID=inspired-bebop-399607 VITE_FIREBASE_STORAGE_BUCKET=inspired-bebop-399607.appspot.com VITE_FIREBASE_MESSAGING_SENDER_ID=368714610861 -VITE_FIREBASE_APP_ID=1:368714610861:web:25a05efd0381cbd552ee24 \ No newline at end of file +VITE_FIREBASE_APP_ID=1:368714610861:web:25a05efd0381cbd552ee24 diff --git a/src/app/apiSlice.ts b/src/app/apiSlice.ts new file mode 100644 index 0000000..9f83b09 --- /dev/null +++ b/src/app/apiSlice.ts @@ -0,0 +1,15 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +const baseQuery = fetchBaseQuery({ + baseUrl: import.meta.env.VITE_BACKEND_API_URL, + prepareHeaders: (headers) => { + headers.append('Access-Control-Allow-Origin', '*') + return headers + }, +}); + +export const appApiSlice = createApi({ + baseQuery, + reducerPath: 'wega-api', + endpoints: () => ({}) +}); \ No newline at end of file diff --git a/src/app/blockchainApiSlice.ts b/src/app/blockchainApiSlice.ts new file mode 100644 index 0000000..e09962b --- /dev/null +++ b/src/app/blockchainApiSlice.ts @@ -0,0 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +import { createApi } from '@reduxjs/toolkit/query/react'; +import { customBlockchainBaseQuery } from '../libs/rtk'; +import { BlockchainAPIBase } from '../libs/wagmi'; + +const baseUrl = 'wega-blockchain-api' +const baseQuery = customBlockchainBaseQuery({ baseUrl }, BlockchainAPIBase); +export const blockchainApiSlice = createApi({ + baseQuery, + reducerPath: baseUrl, + endpoints: () => ({}) +}); + + \ No newline at end of file diff --git a/src/app/store.ts b/src/app/store.ts index b8377ad..05b61f1 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -1,23 +1,30 @@ import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; import appReducer from '../containers/App/AppSlice' -import { appApiSlice } from '../containers/App/api' +import { appApiSlice } from '../containers/App/api'; +import { blockchainApiSlice } from './blockchainApiSlice'; +import { appApiSlice as newAppApiSlice } from './apiSlice'; import intlReducer from '../containers/LanguageProvider/intlSlice' import blockchainReducer from '../api/blockchain/blockchainSlice' - export const store = configureStore({ reducer: { [appApiSlice.reducerPath]: appApiSlice.reducer, + [newAppApiSlice.reducerPath]: newAppApiSlice.reducer, + [blockchainApiSlice.reducerPath]: blockchainApiSlice.reducer, blockchain: blockchainReducer, app: appReducer, language: intlReducer, - }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false - }).concat(appApiSlice.middleware), + }).concat( + appApiSlice.middleware, + newAppApiSlice.middleware, + blockchainApiSlice.middleware + ), devTools: import.meta.env.VITE_REDUX_DEBUG === "true" ? true : false }); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType; -export type AppThunk = ThunkAction>; +export type AppThunk = ThunkAction +>; \ No newline at end of file diff --git a/src/assets/icons/BarCoinIcon.tsx b/src/assets/icons/BarCoinIcon.tsx index f4942d6..e6c5418 100644 --- a/src/assets/icons/BarCoinIcon.tsx +++ b/src/assets/icons/BarCoinIcon.tsx @@ -1,22 +1,23 @@ +import * as React from "react" import { SVGProps } from "react" const SvgComponent = (props: SVGProps) => ( ) => ( ) => ( - - + {/* */} + + + + ) export default SvgComponent diff --git a/src/assets/icons/CoinSideTailsIconWithCircle.tsx b/src/assets/icons/CoinSideTailsIconWithCircle.tsx index 9f6bb52..9c7b79c 100644 --- a/src/assets/icons/CoinSideTailsIconWithCircle.tsx +++ b/src/assets/icons/CoinSideTailsIconWithCircle.tsx @@ -3,28 +3,19 @@ import { SVGProps } from "react" const SvgComponent = (props: SVGProps) => ( - ) export default SvgComponent diff --git a/src/common/GameBar/GameBarHeader.tsx b/src/common/GameBar/GameBarHeader.tsx index e30b1e1..f0c731d 100644 --- a/src/common/GameBar/GameBarHeader.tsx +++ b/src/common/GameBar/GameBarHeader.tsx @@ -6,5 +6,4 @@ export const GameBarHeader: React.FC = (props: React.Attribute Date created Game Wager - Escrow \ No newline at end of file diff --git a/src/common/GameBar/GameBarLoadingSkeleton.tsx b/src/common/GameBar/GameBarLoadingSkeleton.tsx new file mode 100644 index 0000000..dd0149c --- /dev/null +++ b/src/common/GameBar/GameBarLoadingSkeleton.tsx @@ -0,0 +1,18 @@ +export const GameBarLoadingSkeleton = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
) +} diff --git a/src/common/GameBar/index.tsx b/src/common/GameBar/index.tsx index 65c8d77..3ce9215 100644 --- a/src/common/GameBar/index.tsx +++ b/src/common/GameBar/index.tsx @@ -17,10 +17,11 @@ import { AllPossibleCurrencyTypes, AllPossibleWagerTypes } from '../../models'; +import { useSelector } from 'react-redux'; import { dateFromTs } from '../../utils'; -import{ BarDiceIcon, BarCoinIcon, USDCIcon, USDTIcon} from '../../assets/icons'; -import { selectGameById } from '../../containers/App/api'; -import { useSelector } from 'react-redux' +import { BarDiceIcon, BarCoinIcon, USDCIcon, USDTIcon } from '../../assets/icons'; +import { selectGameById } from '../../components/WegaGames/apiSlice'; +import { GameBarLoadingSkeleton } from '../GameBar/GameBarLoadingSkeleton'; import { utils } from 'ethers'; import { ButtonForJoinableGame } from '../ButtonForJoinableGame'; import { ButtonForWaitingGame } from '../ButtonForWaitingGame'; @@ -43,16 +44,15 @@ function GameBar({ ...rest }: { gameId: number } & React.Attributes & Partial> & GameBarProps) { const game = useSelector(state => selectGameById(state, gameId)); - const { user } = useWegaStore() - - return game && user?.uuid && ( + const { user } = useWegaStore(); + return game && user?.uuid ? ( {/* date */} {dateFromTs(new Date(game.createdAt as string).getTime() * 1000)} - + {renderGameTypeBadge(game.gameType)} - {BADGE_TEXTS[game.gameType]} + {BADGE_TEXTS[game.gameType]} @@ -76,7 +76,7 @@ function GameBar({ } - ) + ) : } export default GameBar; diff --git a/src/common/GameBar/types.ts b/src/common/GameBar/types.ts index d480656..f0e88f4 100644 --- a/src/common/GameBar/types.ts +++ b/src/common/GameBar/types.ts @@ -72,4 +72,4 @@ export const BarHeaderColumn = styled.span` font-weight: 400; line-height: 19px; ${tw`text-shinishi`} -` \ No newline at end of file +` diff --git a/src/common/ToggleCoinFlipSides/index.tsx b/src/common/ToggleCoinFlipSides/index.tsx index 655362c..5d132e0 100644 --- a/src/common/ToggleCoinFlipSides/index.tsx +++ b/src/common/ToggleCoinFlipSides/index.tsx @@ -61,7 +61,7 @@ export const ToggleCoinFlipSides: React.FC = ({ setCur css={[isTAILSSelected && selectedStyles]} > {/* icon */} - + TAILS ) diff --git a/src/components/CreateGameCard/CreateDiceGameCard.tsx b/src/components/CreateGameCard/CreateDiceGameCard.tsx index c5e0f0b..85d46e6 100644 --- a/src/components/CreateGameCard/CreateDiceGameCard.tsx +++ b/src/components/CreateGameCard/CreateDiceGameCard.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import { utils } from 'ethers'; import Joi from 'joi'; import { CreateGameCardContainer, @@ -16,6 +17,7 @@ import { AllPossibleWagerTypes, HexishString, AllPossibleWegaTypes, + Network, } from "../../models"; import { BadgeIcon, @@ -29,15 +31,18 @@ import { ArrowDownIcon, StarLoaderIcon } from '../../assets/icons'; import tw from 'twin.macro'; import { useForm } from 'react-hook-form'; import { useBalance } from 'wagmi'; -import { useBlockchainApiHooks, useAppSelector, useNavigateTo } from '../../hooks'; -import { selectWagerApproved } from '../../api/blockchain/blockchainSlice'; +import { useNavigateTo } from '../../hooks'; import { useCreateGameMutation } from '../../containers/App/api'; +import { + useCreateWagerAndDepositMutation, + useAllowanceQuery, + useApproveERC20Mutation, +} from './blockchainApiSlice'; import toast from 'react-hot-toast'; -import { toastSettings } from '../../utils'; +import { toastSettings, toBigIntInWei, escrowConfig, parseTopicDataFromEventLog } from '../../utils'; import Button from '../../common/Button'; import { ToggleWagerBadge } from '../../common/ToggleWagerBadge'; import { useFormReveal } from './animations'; -import { utils } from 'ethers'; export interface CreateGameCardInterface { wagerType: AllPossibleWagerTypes; @@ -46,6 +51,7 @@ export interface CreateGameCardInterface { playerAddress: HexishString; gameType: AllPossibleWegaTypes; playerUuid: string; + network: Network; } export const CreateDiceGameCard = ({ @@ -55,6 +61,7 @@ export const CreateDiceGameCard = ({ playerAddress, playerUuid, gameType, + network, // eslint-disable-next-line @typescript-eslint/no-unused-vars css, ...rest @@ -65,14 +72,7 @@ export const CreateDiceGameCard = ({ const [currentWagerType] = useState(wagerType); const [currentCurrencyType, setCurrentCurrencyType] = useState(currencyType); const {revealed, triggerRevealAnimation} = useFormReveal(false, formRef, detailsBlock); - - const { - useAllowanceQuery, - useApproveERC20Mutation, - useCreateWagerMutation, - } = useBlockchainApiHooks; - - const { register, formState: { errors }, getValues, watch, handleSubmit, setValue } = useForm({ + const { register, formState: { errors }, watch, handleSubmit, setValue } = useForm({ mode: 'onChange', resolver: joiResolver(createGameSchema('wager')) , reValidateMode: 'onChange', @@ -80,40 +80,50 @@ export const CreateDiceGameCard = ({ wager: 1 } }); + // approval for allowance - const isWagerApproved = useAppSelector(state => selectWagerApproved(state)); - const { isLoading: isGetAllowanceLoading, allowance } = useAllowanceQuery(); + const isWagerApproved = (allowance: number, wagerAmount: number) => allowance >= wagerAmount; + const allowanceQuery = useAllowanceQuery({ + spender: escrowConfig.address[network.id as keyof typeof escrowConfig.address], + owner: playerAddress, + tokenAddress, + }); // get token balance of user const { data: userWagerBalance, isLoading: isWagerbalanceLoading } = useBalance({ address: playerAddress, token: tokenAddress, }) + + // create game + const [approveERC20, approveERC20Query] = useApproveERC20Mutation(); + const [createWagerAndDeposit, createWagerAndDepositQuery] = useCreateWagerAndDepositMutation(); - // approve token - const { isLoading: isApproveERC20Loading, approveERC20 } = useApproveERC20Mutation(); - const handleApproveWagerClick = ({ wager }: { wager: number }) => { - approveERC20(tokenAddress, wager); - }; + const [ createGame, { + isLoading: isCreateGameLoading, + status: createGameStatus, + data: createGameResponse + }] = useCreateGameMutation(); - // create game - const { isLoading: isCreateWagerLoading, createWager } = useCreateWagerMutation(); - const [ createGame, { isLoading: isCreateGameLoading, status: createGameStatus, data: createGameResponse } ] = useCreateGameMutation(); const handleCreateGameClick = async ({ wager }: { wager: number }) => { try { - const createWagerData = await createWager({ tokenAddress, playerAddress, accountsCount: 2, wager, gameType }).unwrap(); + if(!isWagerApproved(allowanceQuery.data, wager)) { + await approveERC20({ spender: escrowConfig.address[network.id as keyof typeof escrowConfig.address], wagerAsBigint: toBigIntInWei(wager), tokenAddress }).unwrap(); + } + const receipt = await createWagerAndDeposit({ tokenAddress, wagerAsBigint: toBigIntInWei(wager), gameType }).unwrap(); + const { escrowHash, nonce } = parseTopicDataFromEventLog(receipt.logs[3], ['event GameCreation(bytes32 indexed escrowHash, uint256 indexed nonce, address creator, string name)']); await createGame({ gameType, players: [ { uuid: playerUuid } ], creatorUuid: playerUuid, wager: { wagerType: currentWagerType.toUpperCase() as AllPossibleWagerTypes, - wagerHash: createWagerData.wagerId as string, + wagerHash: escrowHash, tokenAddress, wagerAmount: utils.parseEther(String(wager)).toString(), wagerCurrency: currentCurrencyType, - nonce: createWagerData.nonce, + nonce: nonce.toNumber(), } }).unwrap(); toast.success('Create game success', { ...toastSettings('success', 'top-center') as any }); @@ -128,10 +138,10 @@ export const CreateDiceGameCard = ({ e.preventDefault(); setValue("wager", wagerAmount); } - + const navigateToGameUi = useNavigateTo() useEffect(() => { - allowance(tokenAddress, playerAddress, getValues('wager')); + allowanceQuery.refetch(); if(createGameStatus === 'fulfilled' && createGameResponse) { navigateToGameUi(`/${gameType.toLowerCase()}/play/${createGameResponse.uuid}`, 1500, { replace: true, state: { gameId: createGameResponse.id, gameUuid: createGameResponse.uuid } @@ -148,7 +158,7 @@ export const CreateDiceGameCard = ({ return (
@@ -203,19 +213,17 @@ export const CreateDiceGameCard = ({ {/* */} { - isWagerApproved ? : - + {( + approveERC20Query.isLoading || + createWagerAndDepositQuery.isLoading || + isCreateGameLoading + ) ? "Loading..." : "Start game" } + + } {/* button approve */} {/* button start game */} - {/* details */} {/* wager */}
diff --git a/src/components/CreateGameCard/apiSlice.ts b/src/components/CreateGameCard/apiSlice.ts new file mode 100644 index 0000000..2c8e931 --- /dev/null +++ b/src/components/CreateGameCard/apiSlice.ts @@ -0,0 +1,33 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +import { appApiSlice } from '../../app/apiSlice'; +import { + type Wega, + type Player, + type Wager, + type AllPossibleWegaTypes, + type WegaAttributes +} from '../../models' + +export const createGameApiSlice = appApiSlice.injectEndpoints({ + endpoints: (builder) => ({ + createGame: builder.mutation & Pick & Pick & Pick & Pick & Partial>({ + query: ({ wager, players, gameType, creatorUuid, gameAttributes }: { + wager: Wager, + players: Player[], + gameType: AllPossibleWegaTypes, + creatorUuid: string; + gameAttributes?: WegaAttributes; + }) => ({ + url: '/games', + method: 'POST', + body: { wager, players, gameType, creatorUuid, gameAttributes } + }), + }), + }), +}) +appApiSlice.enhanceEndpoints({ + addTagTypes: ['Games'], + endpoints: { createGame: { invalidatesTags: [ { type: 'Games', id: 'LIST' } ]} } +}); + +export const { useCreateGameMutation } = createGameApiSlice; diff --git a/src/components/CreateGameCard/blockchainApi.ts b/src/components/CreateGameCard/blockchainApi.ts new file mode 100644 index 0000000..5189256 --- /dev/null +++ b/src/components/CreateGameCard/blockchainApi.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { utils } from 'ethers'; +import { prepareWriteContract } from '@wagmi/core'; +import { gameControllerConfig } from "../../utils"; +import { HexishString, AllPossibleWegaTypes } from '../../models'; +import { BlockchainAPIBase } from '../../libs/wagmi' + +export class CreateGameBlockchainApi extends BlockchainAPIBase { + private gameControllerConfig = { + address: gameControllerConfig.address[this.chain?.id as keyof typeof gameControllerConfig.address] as HexishString, + abi: gameControllerConfig.abi, + } + public functions = { + createWagerAndDeposit: async ({ tokenAddress, gameType, wager }: { + tokenAddress: HexishString + wager: number, + gameType: AllPossibleWegaTypes, + }) => { + const wagerAsBigint = utils.parseEther(String(wager)).toBigInt() + const config = await prepareWriteContract({ + ...this.gameControllerConfig, + functionName: 'createGame', + args: [gameType, tokenAddress, wagerAsBigint] + }) + return await this.handleWriteRequest(config); + } + } + constructor(baseUrl: string | undefined = undefined) { + super(baseUrl); + } +} diff --git a/src/components/CreateGameCard/blockchainApiSlice.ts b/src/components/CreateGameCard/blockchainApiSlice.ts new file mode 100644 index 0000000..5b7f4e5 --- /dev/null +++ b/src/components/CreateGameCard/blockchainApiSlice.ts @@ -0,0 +1,67 @@ +import { blockchainApiSlice } from '../../app/blockchainApiSlice'; +import { + AllPossibleWegaTypes, + HexishString +} from '../../models'; +import { ContractTypes } from '../../libs/wagmi'; +import { utils } from 'ethers'; + +// Todo + // write function names with type safety +export const createGameBlockchainApiSlice = blockchainApiSlice.injectEndpoints({ + endpoints: (builder) => ({ + createWagerAndDeposit: builder.mutation({ + query: ({ tokenAddress, wagerAsBigint, gameType }) => ({ + functionName: 'createGame', + contract: ContractTypes.GAMECONTROLLER, + method: 'WRITE', + args: [gameType, tokenAddress, wagerAsBigint] + }) + }), + approveERC20: builder.mutation({ + query: ({ spender, wagerAsBigint, tokenAddress }) => ({ + functionName: 'approve', + method: 'WRITE', + contract: ContractTypes.TOKEN, + contractAddress: tokenAddress, + args: [spender, wagerAsBigint] + }), + }), + allowance: builder.query ({ + query: ({ owner, spender, tokenAddress }) => ({ + functionName: 'allowance', + method: 'READ', + contract: ContractTypes.TOKEN, + contractAddress: tokenAddress, + args: [owner, spender] + }), + transformResponse: (response: any) => utils.formatEther(response.toString()) + }), + hash: builder.query ({ + query: ({ tokenAddress, numPlayers, playerAddress, nonce, wagerAsBigint }) => ({ + functionName: 'hash', + method: 'READ', + contract: ContractTypes.ERC20ESCROW, + args: [tokenAddress, playerAddress, numPlayers, wagerAsBigint, nonce] + }), + }), + currentNonce: builder.query ({ + query: ({ playerAddress }) => ({ + functionName: 'currentNonce', + method: 'READ', + contract: ContractTypes.ERC20ESCROW, + args: [playerAddress] + }), + }) + }) +}); + +export const { + useCreateWagerAndDepositMutation, + useApproveERC20Mutation, + useAllowanceQuery, + useHashQuery, + useCurrentNonceQuery, +} = createGameBlockchainApiSlice; + + \ No newline at end of file diff --git a/src/components/CreateGameCard/index.tsx b/src/components/CreateGameCard/index.tsx index 81298c8..1ba7a1b 100644 --- a/src/components/CreateGameCard/index.tsx +++ b/src/components/CreateGameCard/index.tsx @@ -4,7 +4,8 @@ import { HexishString, AllPossibleWegaTypes, WegaTypesEnum, - WegaTypes + WegaTypes, + Network } from "../../models"; import { CreateDiceGameCard } from './CreateDiceGameCard'; import { CreateCoinFlipGameCard } from './CreateCoinFlipGameCard'; @@ -16,6 +17,7 @@ export interface CreateGameCardInterface extends React.Attributes, React.AllHTML playerAddress: HexishString; gameType: AllPossibleWegaTypes; playerUuid: string; + network: Network; } const CREATE_GAME_CARD_COMPONENTS: any = { @@ -31,6 +33,7 @@ const CreateGameCard = ({ playerUuid, gameType, children, + network, ...rest }: CreateGameCardInterface) => { const renderCard = () => { @@ -47,6 +50,7 @@ const CreateGameCard = ({ playerAddress, playerUuid, gameType, + network, ...rest } }>{children} } else { @@ -57,6 +61,7 @@ const CreateGameCard = ({ playerAddress, playerUuid, gameType, + network, ...rest } } diff --git a/src/components/JoinGameCard/JoinCoinFlipGameCard.tsx b/src/components/JoinGameCard/JoinCoinFlipGameCard.tsx index 6c65b30..ab8c6eb 100644 --- a/src/components/JoinGameCard/JoinCoinFlipGameCard.tsx +++ b/src/components/JoinGameCard/JoinCoinFlipGameCard.tsx @@ -18,6 +18,7 @@ import { AllPossibleWegaTypes, AllPossibleCoinSides, WegaAttributes, + WegaState } from "../../models"; import { BadgeIcon, @@ -114,9 +115,21 @@ const JoinCoinFlipGameCard = ({ const playerChoices = [Number(gameAttributes[0].value), currentCoinSide]; await depositWager(escrowId, playerChoices).unwrap(); await joinGame({ newPlayerUuid: playerUuid, gameUuid }).unwrap(); - await updateGame({ uuid: gameUuid, gameAttributes: [{ key: "players[1].flipChoice", value: currentCoinSide.toString()}, ...gameAttributes] }).unwrap(); + await updateGame({ + uuid: gameUuid, + state: WegaState.PLAYING, + gameAttributes: [ + { key: "players[1].flipChoice", value: currentCoinSide.toString()}, + ...gameAttributes + ] + } + ).unwrap(); toast.success('Deposit success', { ...toastSettings('success', 'top-center') as any }); - navigateToGameUi(`/${gameType.toLowerCase()}/play/${gameUuid}`, 1500, { replace: true, state: { gameId, gameUuid } }); + navigateToGameUi(`/${gameType.toLowerCase()}/play/${gameUuid}`, 1500, { + replace: true, + state: { + gameId, gameUuid + } }); } catch (e: any){ console.log(e) const message = e?.message ?? 'Deposit error' @@ -129,7 +142,7 @@ const JoinCoinFlipGameCard = ({ const handleApproveWagerClick = ({ wager }: { wager: number }) => { approveERC20(tokenAddress, wager); }; - + // const navigateToGameUi = useNavigateTo() useEffect(() => { allowance(tokenAddress, playerAddress, wagerAmount); diff --git a/src/components/JoinGameCard/JoinDiceGamecard.tsx b/src/components/JoinGameCard/JoinDiceGamecard.tsx index 6bfb10d..be72954 100644 --- a/src/components/JoinGameCard/JoinDiceGamecard.tsx +++ b/src/components/JoinGameCard/JoinDiceGamecard.tsx @@ -13,7 +13,8 @@ import { } from '../../common/JoinableGameBar/types'; import { AllPossibleCurrencyTypes, - AllPossibleWagerTypes, + AllPossibleWagerTypes, + WegaState } from "../../models"; import { BadgeIcon, @@ -33,7 +34,7 @@ import toast from 'react-hot-toast'; import { toastSettings } from '../../utils'; import Button from '../../common/Button'; import { useFormReveal } from '../CreateGameCard/animations'; -import { useJoinGameMutation } from '../../containers/App/api'; +import { useJoinGameMutation, useUpdateGameMutation } from '../../containers/App/api'; import { JoinGameCardProps } from './' export interface JoinDiceGameCardProps extends JoinGameCardProps, React.Attributes, React.AllHTMLAttributes {}; @@ -61,6 +62,7 @@ const JoinGameDiceCard: React.FC = ({ const isWagerApproved = useAppSelector(state => selectWagerApproved(state)); const {revealed, triggerRevealAnimation} = useFormReveal(false, formRef, detailsBlock); + // should go into blockchain api slice const { useAllowanceQuery, useApproveERC20Mutation, @@ -75,21 +77,24 @@ const JoinGameDiceCard: React.FC = ({ wager: wagerAmount, } }); + // approval for allowance const { isLoading: isGetAllowanceLoading, allowance } = useAllowanceQuery(); - // get token balance of userP + // get token balance of user const { data: userWagerBalance, isLoading: isWagerbalanceLoading } = useBalance({ address: playerAddress, token: tokenAddress, }) + const [ updateGame, { isLoading: isUpdateGameLoading } ] = useUpdateGameMutation(); const { isLoading: isDepositWagerLoading, depositWager } = useDepositWagerMutation(); const [ joinGame, { isLoading: isJoinGameLoading } ] = useJoinGameMutation(); const handleDepositWagerClick = async () => { try { await depositWager(escrowId).unwrap(); await joinGame({ newPlayerUuid: playerUuid, gameUuid }).unwrap(); + await updateGame({ uuid: gameUuid, state: WegaState.PLAYING }).unwrap(); navigateToGameUi(`/${gameType.toLowerCase()}/play/${gameUuid}`, 1500, { replace: true, state: { gameId: gameId, gameUuid } }); toast.success('Deposit success', { ...toastSettings('success', 'top-center') as any }); } catch (e: any){ @@ -146,8 +151,8 @@ const JoinGameDiceCard: React.FC = ({ {/* */} { isWagerApproved ? : diff --git a/src/components/WegaGames/apiSlice.ts b/src/components/WegaGames/apiSlice.ts new file mode 100644 index 0000000..a47f69d --- /dev/null +++ b/src/components/WegaGames/apiSlice.ts @@ -0,0 +1,41 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +import { createEntityAdapter, createSelector } from '@reduxjs/toolkit'; +import { appApiSlice } from '../../app/apiSlice'; +import { Wega } from '../../models'; + +export const gamesApiSlice = appApiSlice.injectEndpoints({ + endpoints: (builder) => ({ + getGames: builder.query({ + query: (filterData = undefined) => { + return ({ + url: filterData ? `/games?${Object.keys(filterData).map((key: any, index: number) => index !== 0 ? `&${key}=${filterData[key]}` : `${key}=${filterData[key]}` ).join('')}` : '/games?sort=-createdAt', + method: 'GET', + }) + }, + providesTags: (result) => result ? [ { type: 'Games', id: 'LIST' }, ...result.ids.map((id: any) => ({ type: 'Game' as const, id })) ] : [{ type: 'Games', id: 'LIST' }], + transformResponse: (response: any) => { + return gamesAdapter.setAll(gamesInitialState, response.items) + } + }), + }) +}) +gamesApiSlice.enhanceEndpoints({ addTagTypes: ['Games'] }); + +export const { useGetGamesQuery } = gamesApiSlice; +const gamesAdapter = createEntityAdapter({ + sortComparer: (a, b) => { + return a.createdAt.localeCompare(b.createdAt); + }, +}); + +// games +export const gamesInitialState = gamesAdapter.getInitialState(); +export const selectGamesResult = gamesApiSlice.endpoints.getGames.select(undefined); +const selectGamesData = createSelector(selectGamesResult, (gamesResult) => gamesResult.data); + +export const { + selectAll: selectAllGames, + selectById: selectGameById, + selectIds: selectAllGamesIds, + selectTotal: selectGamesCount, +} = gamesAdapter.getSelectors((state: any) => selectGamesData(state) ?? gamesInitialState); \ No newline at end of file diff --git a/src/components/WegaGames/index.tsx b/src/components/WegaGames/index.tsx new file mode 100644 index 0000000..e32b91b --- /dev/null +++ b/src/components/WegaGames/index.tsx @@ -0,0 +1,91 @@ +import { useEffect, useState } from 'react'; +import GameBar from "../../common/GameBar"; +import Section from '../../common/Section'; +import { JoinableGamesHeaderBar } from '../../common/JoinableGameBar/types'; +import { useGetGamesQuery } from './apiSlice'; +import { Wega, WegaState } from '../../models'; +import 'twin.macro'; + +export interface JoinableAndPlayableGamesProps extends React.Attributes { + userUuid: string; + gamesCount: number; +} +const filterPlayableGames = (data: Wega[], userUuid: string) => data.filter(game => game.state === WegaState.PLAYING && game.players.some(predicate => predicate.uuid === userUuid )); +const filterJoinableGames = (data: Wega[], userUuid: string) => data.filter(game => game.state === WegaState.PENDING && game.players.every(predicate => predicate.uuid !== userUuid )); +const sortPlayableGames = (data: Wega[]) => data.sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + +export const JoinableAndPlayableGames: React.FC = ({ gamesCount, userUuid, ...rest }: JoinableAndPlayableGamesProps) => { + const { data, isLoading, isSuccess} = useGetGamesQuery(undefined); + const [gameIds, setGameIds] = useState(); + useEffect(() => { + if(isSuccess && data && data?.entities) { + const dataArray = data.ids.map((id: number) => data.entities[id]) as Wega[]; + const playableGames = filterPlayableGames(dataArray, userUuid); + const joinableGames = filterJoinableGames(dataArray, userUuid); + const sortedGameIds = sortPlayableGames([...playableGames, ...joinableGames]).map(game => game.id); + setGameIds(sortedGameIds); + } + }, [data, gamesCount, isSuccess]); + return (
+ + Date created + Game + Wager + Escrow + + { + !isLoading && gameIds && gameIds.map((gameId: number) => ( )) + } +
+ ) +} + +export const JoinableGames: React.FC = ({ gamesCount, userUuid, ...rest }: JoinableAndPlayableGamesProps) => { + const { data, isLoading, isSuccess} = useGetGamesQuery({ state: WegaState.PENDING }); + const [gameIds, setGameIds] = useState(); + useEffect(() => { + if(isSuccess && data && data?.entities) { + const dataArray = data.ids.map((id: number) => data.entities[id]) as Wega[]; + const joinableGames = filterJoinableGames(dataArray, userUuid); + const sortedGameIds = sortPlayableGames(joinableGames).map(game => game.id); + setGameIds(sortedGameIds ?? []); + } + }, [data, gamesCount]); + return (
+ + Date created + Game + Wager + Escrow + + { + !isLoading && gameIds && gameIds.map((gameId: number) => ()) + } +
+ ) +} + +export const PlayableGames: React.FC = ({ gamesCount, userUuid, ...rest }: JoinableAndPlayableGamesProps) => { + const { data, isLoading, isSuccess } = useGetGamesQuery({ state: WegaState.PLAYING }); + const [gameIds, setGameIds] = useState(); + useEffect(() => { + if(isSuccess && data) { + const dataArray = data.ids.map((id: number) => data.entities[id]) as Wega[]; + const joinableGames = filterJoinableGames(dataArray, userUuid); + const sortedGameIds = sortPlayableGames(joinableGames).map(game => game.id); + setGameIds(sortedGameIds ?? []); + } + }, [data, gamesCount]); + return (
+ + Date created + Game + Wager + Escrow + + { + !isLoading && gameIds && gameIds.map((gameId: number) => ()) + } +
+ ) +} diff --git a/src/containers/App/api.ts b/src/containers/App/api.ts index e1048aa..2ada80c 100644 --- a/src/containers/App/api.ts +++ b/src/containers/App/api.ts @@ -51,14 +51,16 @@ export const appApiSlice = createApi({ }, invalidatesTags: () => [ { type: 'Games', id: 'LIST' } ] }), - getGames: builder.query({ - query: () => ({ - url: '/games', - method: 'GET', - }), + getGames: builder.query({ + query: (filterData) => { + return ({ + url: filterData ? `/games?${Object.keys(filterData).map((key: any, index: number) => index !== 0 ? `&${key}=${filterData[key]}` : `${key}=${filterData[key]}` ).join('')}` : '/games?sort=-createdAt', + method: 'GET', + }) + }, providesTags: (result) => result ? [ { type: 'Games', id: 'LIST' }, ...result.ids.map((id: any) => ({ type: 'Game' as const, id })) ] : [{ type: 'Games', id: 'LIST' }], transformResponse: (response: any) => { - return gamesAdapter.setAll(gamesInitialState, response.items) + return gamesAdapter.setAll(gamesInitialState, response.items) } }), createGame: builder.mutation & Pick & Pick & Pick & Pick & Partial>({ @@ -88,20 +90,19 @@ export const { const gamesAdapter = createEntityAdapter({ sortComparer: (a, b) => { - return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() < 0 ? 0 : 1; + return a.createdAt.localeCompare(b.createdAt); }, // sorts from most recent to later }); // games export const gamesInitialState = gamesAdapter.getInitialState(); -export const selectGamesResult = appApiSlice.endpoints.getGames.select(); +export const selectGamesResult = appApiSlice.endpoints.getGames.select(undefined); + const selectGamesData = createSelector(selectGamesResult, (gamesResult) => gamesResult.data); - + export const { selectAll: selectAllGames, selectById: selectGameById, - selectIds: selectAllGamesIds -} = gamesAdapter.getSelectors((state: any) => selectGamesData(state) ?? gamesInitialState); - - - + selectIds: selectAllGamesIds, + selectTotal: selectGamesCount, +} = gamesAdapter.getSelectors((state: any) => selectGamesData(state) ?? gamesInitialState); \ No newline at end of file diff --git a/src/containers/App/index.tsx b/src/containers/App/index.tsx index 4fb8c1a..30620da 100644 --- a/src/containers/App/index.tsx +++ b/src/containers/App/index.tsx @@ -4,14 +4,11 @@ import LanguageProvider from '../LanguageProvider'; import { localeSelector } from '../LanguageProvider/intlSlice'; import { useAppSelector } from '../../hooks/useAppDispatch'; import { RouterProvider } from 'react-router-dom'; -import { appApiSlice } from './api'; -import { store } from '../../app/store'; import router from '../Router'; import 'twin.macro'; function App() { - store.dispatch(appApiSlice.endpoints.getGames.initiate()); const locale = useAppSelector(state => localeSelector(state)); useEffect(()=>{ diff --git a/src/containers/CreateGamePage/index.tsx b/src/containers/CreateGamePage/index.tsx index fad523c..6a3584c 100644 --- a/src/containers/CreateGamePage/index.tsx +++ b/src/containers/CreateGamePage/index.tsx @@ -3,41 +3,38 @@ import { useLocation } from 'react-router-dom'; import Section from '../../common/Section'; import { SectionHeaderContainer, SectionHeaderTitle } from '../../common/Section/types'; import CreateGameCard from '../../components/CreateGameCard'; -import PlayableGamesSection from '../../components/PlayableGamesSection'; -import { WagerTypes, WagerTypesEnum, CurrencyTypes, CurrencyTypesEnum, AllPossibleWegaTypes, WegaTypesEnum, WegaTypes } from '../../models'; +import { + WagerTypes, + WagerTypesEnum, + CurrencyTypes, + CurrencyTypesEnum, + AllPossibleWegaTypes, + WegaTypesEnum, + WegaTypes +} from '../../models'; import { SupportedWagerTokenAddresses } from '../../models/constants'; -import { useGetGamesQuery } from '../App/api'; import { useWegaStore } from '../../hooks'; import { ComponentLoader } from '../../common/loaders' import MainContainer from '../../components/MainContainer'; +import { FloatingOrbs } from "../../common/FloatingOrbs"; import { BADGE_TEXTS } from '../../common/GameBar'; -import { MinimumGameRounds } from '../../components/PlayGameSection/types'; import 'twin.macro'; const CreateGamePage = () => { const { state } = useLocation(); const { user, network, wallet } = useWegaStore(); - const { isLoading, joinableGameIds } = useGetGamesQuery(undefined, { - selectFromResult: ({ data, isLoading, isSuccess }) => ({ - joinableGameIds: data ? isSuccess && Object.entries(data.entities) - .filter(([, game]: any) => - game.creatorUuid !== user.uuid && (game.currentTurn !== (game.gameType === WegaTypes[WegaTypesEnum.COINFLIP] ? 1 : MinimumGameRounds[game.gameType] * game.requiredPlayerNum))) - .map(([id,]: any) => Number(id)) : [], - isLoading, - }) - } - ); return ( - !isLoading && + network && network?.id && wallet && user.uuid && state - ) ? (<> + ) ? (
Create - {BADGE_TEXTS[state.gameType.toUpperCase()]} +
{ playerAddress={wallet.address} gameType={state.gameType.toUpperCase() as AllPossibleWegaTypes} playerUuid={user.uuid} + network={network} />
-
- ) : +
) : } export default CreateGamePage; diff --git a/src/containers/JoinGamePage/index.tsx b/src/containers/JoinGamePage/index.tsx index 2b42f97..b4c21e9 100644 --- a/src/containers/JoinGamePage/index.tsx +++ b/src/containers/JoinGamePage/index.tsx @@ -2,54 +2,51 @@ import { Helmet } from 'react-helmet-async'; import Section from '../../common/Section'; import { SectionHeaderTitle, SectionHeaderContainer } from '../../common/Section/types'; import JoinGameCard from '../../components/JoinGameCard'; -import { AllPossibleWegaTypes, HexishString, Wega } from '../../models'; +import { AllPossibleWegaTypes, HexishString } from '../../models'; import { SupportedWagerTokenAddresses } from '../../models/constants'; -import { selectAllGames } from '../App/api'; -import { useSelector } from 'react-redux'; -import { useWegaStore } from '../../hooks'; +import { useWegaStore, useFirebaseData } from '../../hooks'; import { ComponentLoader } from '../../common/loaders' import { useParams } from 'react-router-dom'; import MainContainer from '../../components/MainContainer'; import { BADGE_TEXTS } from "../../common/JoinableGameBar"; +import { FloatingOrbs } from "../../common/FloatingOrbs" import { utils } from 'ethers'; import 'twin.macro'; -// TODO - // REF retrieval of game const JoinGamePage = () => { - const { user, network, wallet } = useWegaStore(); - const params = useParams(); - const games = useSelector(state => selectAllGames(state)); - const game: Wega = games.filter(g => g.uuid === params.id)[0]; - return (network?.id && wallet && user.uuid && game) ? ( - <> - - Join - {BADGE_TEXTS[game.gameType]} - - -
- Match wager - } - > - -
-
- ) : + const { user, network, wallet } = useWegaStore(); + const params = useParams(); + const { game } = useFirebaseData(params.id as string); + return (network?.id && wallet && user.uuid && game) ? ( +
+ + Join - {BADGE_TEXTS[game.gameType]} + + + +
+ Match wager + }> + +
+
+
+ ) : } export default JoinGamePage; diff --git a/src/containers/Navigation/index.tsx b/src/containers/Navigation/index.tsx index c776f04..ef9cca8 100644 --- a/src/containers/Navigation/index.tsx +++ b/src/containers/Navigation/index.tsx @@ -34,7 +34,6 @@ const Navigation = () => {
  • Play
  • -
  • Swap
  • Wins
diff --git a/src/containers/PlayPage/index.tsx b/src/containers/PlayPage/index.tsx index 69e5a49..28d3d4a 100644 --- a/src/containers/PlayPage/index.tsx +++ b/src/containers/PlayPage/index.tsx @@ -6,31 +6,15 @@ import { CoinFlipGameCard, RaffleGameCard } from '../../components/GameCard'; -import JoinableOrPlayableGamesSection from '../../components/JoinableOrPlayableGamesSection'; -import { useGetGamesQuery } from '../App/api'; import { ComponentLoader } from '../../common/loaders'; import MainContainer from '../../components/MainContainer'; -import { useWegaStore } from '../../hooks'; -import { MinimumGameRounds } from '../../components/PlayGameSection/types'; -import { WegaTypes, WegaTypesEnum } from '../../models' +import { JoinableAndPlayableGames } from '../../components/WegaGames' +import { useWegaStore, useFirebaseData } from '../../hooks'; import 'twin.macro'; const PlayPage = () => { - const { user } = useWegaStore(); - const { joinableGameIds, isLoading, playableGameIds } = useGetGamesQuery(undefined, { - selectFromResult: ({ data, isLoading, isSuccess }) => ({ - joinableGameIds: data ? - isSuccess && Object.entries(data.entities) - .filter(([, game]: any) => game.creatorUuid !== user.uuid && (game.currentTurn !== (game.gameType === WegaTypes[WegaTypesEnum.COINFLIP] ? 1 : MinimumGameRounds[game.gameType] * game.requiredPlayerNum))) - .map(([id,]: any) => Number(id)) : [], - playableGameIds: data ? - isSuccess && Object.entries(data.entities) - .filter(([, game]: any) => game.creatorUuid === user.uuid && (game.currentTurn !== (game.gameType === WegaTypes[WegaTypesEnum.COINFLIP] ? 1 : MinimumGameRounds[game.gameType] * game.requiredPlayerNum))) - .map(([id,]: any) => Number(id)) : [], - isLoading, - }) - } - ); + const { user } = useWegaStore(); + const { gamesCount } = useFirebaseData(''); return ( <> @@ -38,7 +22,7 @@ const PlayPage = () => {
{
- { !isLoading && joinableGameIds && playableGameIds && user?.uuid ? : } + { user?.uuid ? : } ) diff --git a/src/hooks/useFirebaseData.ts b/src/hooks/useFirebaseData.ts index 8dc7823..758ee50 100644 --- a/src/hooks/useFirebaseData.ts +++ b/src/hooks/useFirebaseData.ts @@ -9,7 +9,8 @@ import { WegaTypes, WegaTypesEnum, AllPossibleCoinSides, - PlayerFlipChoices, + PlayerFlipChoices, + Wega } from '../models'; export function useFirebaseData(gameUuid: string) { @@ -18,11 +19,15 @@ export function useFirebaseData(gameUuid: string) { const [playersInGame, setPlayersInGame] = useGetSet([]); const [wegaAttributes, setWegaAttributes] = useGetSet([]); const [playerFlipChoices, setPlayerFlipChoices] = useGetSet(undefined); + const [gamesCount, setGamesCount] = useGetSet(0); + const [game, setGame] = useGetSet(undefined); const startListeningFirebase = () => { const databaseRef = ref(database); onValue(databaseRef, (snapshot) => { const { games } = snapshot.val(); + setGame(games[gameUuid]); + setGamesCount(Object.keys(games).length); const { players, requiredPlayerNum, currentTurn, gameAttributes, gameType } = games[gameUuid]; if(players.length === requiredPlayerNum) { setIsGamePlayable(true); @@ -59,7 +64,8 @@ export function useFirebaseData(gameUuid: string) { setPlayersInGame, setGameInfo, setWegaAttributes, - setPlayerFlipChoices + setPlayerFlipChoices, + setGamesCount ]); return { @@ -68,18 +74,7 @@ export function useFirebaseData(gameUuid: string) { gameInfo: gameInfo(), gameAttributes: wegaAttributes(), playerFlipChoices: playerFlipChoices(), + gamesCount: gamesCount(), + game: game(), } -} - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/src/libs/rtk/index.ts b/src/libs/rtk/index.ts new file mode 100644 index 0000000..92cdb55 --- /dev/null +++ b/src/libs/rtk/index.ts @@ -0,0 +1,33 @@ +import type { BaseQueryFn } from '@reduxjs/toolkit/query'; +import { HexishString } from '../../models'; +import { BlockchainAPIBase, type AllContractTypes } from '../wagmi'; + +// todo add typechecking for function name +export const customBlockchainBaseQuery = ({ baseUrl }: { baseUrl: string } = { baseUrl: '' }, API: typeof BlockchainAPIBase ): BaseQueryFn< + { + contract: AllContractTypes + method: 'READ'|'WRITE'; + functionName: string; + args: any; + contractAddress?: HexishString, + }, + unknown, + unknown +> => async ({ contract, method, args, functionName, contractAddress }) => { + const api = new API(baseUrl); + try { + if(method === 'WRITE') { + const transactionHash = await api.write({ contract, functionName, args, contractAddress }); + return await api.waitForMined(transactionHash); + } + return await api.read({contract, functionName, args, contractAddress }); + } catch (error: any) { + return { + error: { + status: error?.status ?? undefined, + data: api.handleError(error, 'Something went wrong'), + }, + } + } + } + diff --git a/src/libs/wagmi/index.ts b/src/libs/wagmi/index.ts index f8b4252..f9451e7 100644 --- a/src/libs/wagmi/index.ts +++ b/src/libs/wagmi/index.ts @@ -1,9 +1,14 @@ import '@rainbow-me/rainbowkit/styles.css'; import { getDefaultWallets, connectorsForWallets } from '@rainbow-me/rainbowkit'; import { configureChains, createConfig } from 'wagmi'; +import { getNetwork, Chain, waitForTransaction, writeContract, readContract, prepareWriteContract } from '@wagmi/core'; import { localhost, polygonMumbai } from 'wagmi/chains'; import { alchemyProvider } from 'wagmi/providers/alchemy'; import { publicProvider } from 'wagmi/providers/public'; +import { HexishString } from '../../models'; +import { tokenConfig, escrowConfig, gameControllerConfig } from "../../utils"; + + const { chains, publicClient } = configureChains( [polygonMumbai, localhost], @@ -27,6 +32,100 @@ const wagmiConfig = createConfig({ }); export default wagmiConfig; - export { chains } +// rtk apis +export type ABIConfig = { + readonly address: Record, + readonly abi: any[]; +} + +export interface IBlockchainAPIBase { + baseUrl: string; + chain: (Chain & { unsupported?: boolean | undefined; }) | undefined; +} + +export const ContractTypes = { + 'TOKEN': 'wegaErc20Dummy', + 'ERC20ESCROW': 'wegaErc20Escrow', + 'GAMECONTROLLER': 'wegaGameController' +} as const; + +export const ContractConfig = { + [ContractTypes.ERC20ESCROW]: escrowConfig, + [ContractTypes.TOKEN]: { + address: tokenConfig.address as typeof tokenConfig.address, + abi: tokenConfig.abi as typeof tokenConfig.abi + }, + [ContractTypes.GAMECONTROLLER]: gameControllerConfig, +} as const; + +export type AllContractTypes = typeof ContractTypes[keyof typeof ContractTypes]; + +export class BlockchainAPIBase implements IBlockchainAPIBase { + public baseUrl: string; + public chain = (getNetwork()).chain; + private config = ContractConfig; + constructor(baseUrl: string | undefined = undefined) { this.baseUrl = baseUrl ?? 'blockchainApi/' } + public functions = {}; + handleError(error: any, customMessage: string){ + if (error.message){ + if(error.message.split("\n\n") && error.message.split("\n\n").length > 0) { + return error.message.split("\n\n")[0]; + } + return error.message + } else { + return customMessage; + } + } + async handleWriteRequest(config: any) { + const { hash } = await writeContract(config); + return hash as HexishString; + } + async read({ + contract, + functionName, + args, + contractAddress + }: { contract: string, functionName: any, args: any, contractAddress?: HexishString }) { + const addressConfig = this.config[contract as AllContractTypes].address; + return await this.handleRequest(async () => await readContract({ + address: !contractAddress ? this.config[contract as AllContractTypes].address[this.chain?.id as keyof typeof addressConfig] as HexishString : contractAddress, + // @ts-ignore + abi: this.config[contract].abi, + functionName, + args + }) + ); + } + + async write({ + contract, + functionName, + args, + contractAddress + }: { + contract: string, functionName: any, args: any, contractAddress?: HexishString }) { + const addressConfig = this.config[contract as AllContractTypes].address; + const config = await prepareWriteContract({ + address: !contractAddress ? this.config[contract as AllContractTypes].address[this.chain?.id as keyof typeof addressConfig] as HexishString : contractAddress, + // @ts-ignore + abi: this.config[contract].abi, + functionName, + args + }) + return await this.handleWriteRequest(config); + } + + async waitForMined(hash: HexishString) { + return await this.handleRequest(async () => await waitForTransaction({ hash })); + } + + async handleRequest(func: any) { + try { + return { data: await func() , error: undefined, meta: undefined } + } catch (error) { + return { data: undefined, error, meta: undefined } + } + } +} \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index 06b43ed..ced1361 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -6,7 +6,9 @@ export { WegaTypesEnum, type WegaAttributes, type GameInfoType, - type PlayerFlipChoices + type PlayerFlipChoices, + type AllPossibleWegaStates, + WegaState } from './wega' export { type AllPossibleWagerTypes, WagerTypesEnum, type Wager, WagerTypes } from './wager'; export { type AllPossibleCurrencyTypes, CurrencyTypes, CurrencyTypesEnum } from './currency' diff --git a/src/models/wega.ts b/src/models/wega.ts index 8be4b9e..8350d4f 100644 --- a/src/models/wega.ts +++ b/src/models/wega.ts @@ -31,4 +31,14 @@ export type Wega = { } export type GameInfoType = { currentRound: number, rollerIndex: number, currentTurn: number }; -export type PlayerFlipChoices = { playerOne: AllPossibleCoinSides, playerTwo: AllPossibleCoinSides | undefined } \ No newline at end of file +export type PlayerFlipChoices = { playerOne: AllPossibleCoinSides, playerTwo: AllPossibleCoinSides | undefined } + +// eslint-disable-next-line no-unused-vars +export const WegaState = { + 'PLAYING': 'PLAYING', + 'PENDING': 'PENDING', + 'COMPLETED': 'COMPLETED', + 'CANCELED': 'CANCELED' +} as const; + +export type AllPossibleWegaStates = typeof WegaState[keyof typeof WegaState]; diff --git a/src/utils/abis.ts b/src/utils/abis.ts index 97b3cd8..29a0405 100644 --- a/src/utils/abis.ts +++ b/src/utils/abis.ts @@ -1,4 +1,4 @@ -// Generated by @wagmi/cli@1.3.0 on 9/13/2023 at 3:21:38 AM +// Generated by @wagmi/cli@1.3.0 on 10/11/2023 at 12:04:57 PM ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // AccessControlUpgradeable @@ -1173,6 +1173,7 @@ export const iWegaGameControllerEventsABI = [ anonymous: false, inputs: [ { name: "escrowHash", internalType: "bytes32", type: "bytes32", indexed: true }, + { name: "nonce", internalType: "uint256", type: "uint256", indexed: true }, { name: "creator", internalType: "address", type: "address", indexed: false }, { name: "name", internalType: "string", type: "string", indexed: false } ], @@ -3330,6 +3331,7 @@ export const wegaGameControllerABI = [ anonymous: false, inputs: [ { name: "escrowHash", internalType: "bytes32", type: "bytes32", indexed: true }, + { name: "nonce", internalType: "uint256", type: "uint256", indexed: true }, { name: "creator", internalType: "address", type: "address", indexed: false }, { name: "name", internalType: "string", type: "string", indexed: false } ], diff --git a/src/utils/ethers-helpers.ts b/src/utils/ethers-helpers.ts index 434e4fc..dd05043 100644 --- a/src/utils/ethers-helpers.ts +++ b/src/utils/ethers-helpers.ts @@ -1,4 +1,4 @@ -import { BigNumber } from "ethers"; +import { BigNumber, utils } from "ethers"; import { Player } from '../models' export function parseIntFromBigNumber(val: BigNumber | number) { @@ -8,10 +8,22 @@ export function parseIntFromBigNumber(val: BigNumber | number) { return val; } +export function toBigIntInWei(value: number): bigint { + return utils.parseEther(String(value)).toBigInt() +} + export const miniWalletAddress = (address: `0x${string}` | undefined) => { return address?.slice(0, 6) + "..." } +export function interfaceIdFromAbi(abi: string[]) { + return new utils.Interface(abi); +} + +export function parseTopicDataFromEventLog(txLog: any, eventAbi: string[]){ + return interfaceIdFromAbi(eventAbi).parseLog(txLog).args; +} + export function isGameCreator( connectedPlayerUUid: string | undefined, players: Player | any, diff --git a/src/utils/index.ts b/src/utils/index.ts index e390f2a..bca7c64 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,6 +3,9 @@ export { parseIntFromBigNumber, isGameCreator, miniWalletAddress, + toBigIntInWei, + parseTopicDataFromEventLog, + interfaceIdFromAbi } from './ethers-helpers.ts' export { deployed } from './deployment.ts' export { toastSettings } from './toast.ts' @@ -13,3 +16,8 @@ export { wegaErc20EscrowConfig as escrowConfig, wegaGameControllerConfig as gameControllerConfig, } from './abis.ts' +export { + wegaErc20DummyConfig, + wegaErc20EscrowConfig, + wegaGameControllerConfig, +} from './abis.ts' \ No newline at end of file