From 31fc7d8aeb965142f80ee63f1f0c7df4cd8faae1 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 8 Aug 2024 13:09:34 +0400 Subject: [PATCH 01/17] battleship-zk: signless --- frontend/apps/battleship-zk/src/app/consts.ts | 2 +- .../apps/battleship-zk/src/app/hocs/index.tsx | 19 +- .../src/app/utils/use-make-transaction.ts | 50 +- .../sails/messages/use-cancel-game-message.ts | 18 +- .../sails/messages/use-create-game-message.ts | 22 +- .../messages/use-delete-player-message.ts | 22 +- .../sails/messages/use-join-game-message.ts | 22 +- .../sails/messages/use-leave-game-message.ts | 19 +- .../sails/messages/use-make-move-message.ts | 22 +- .../messages/use-verify-placement-message.ts | 22 +- .../sails/messages/use-make-move-message.ts | 22 +- .../sails/messages/use-start-game-message.ts | 22 +- frontend/apps/racing-car-game/src/utils.ts | 4 +- .../src/context/context.ts | 7 + .../src/context/hooks.ts | 123 +- .../src/context/index.tsx | 143 +- .../signless-transactions/src/context/lib.ts | 1335 +++++++++++++++++ .../src/context/metadata-provider.tsx | 44 + .../src/context/sails-provider.tsx | 36 + .../src/context/types.ts | 38 +- .../signless-transactions/src/hooks/index.ts | 15 +- ...-session.ts => use-create-base-session.ts} | 170 +-- .../src/hooks/use-create-metadata-session.ts | 114 ++ .../src/hooks/use-create-sails-session.ts | 66 + .../signless-transactions/src/index.ts | 10 +- 25 files changed, 2010 insertions(+), 357 deletions(-) create mode 100644 frontend/packages/signless-transactions/src/context/context.ts create mode 100644 frontend/packages/signless-transactions/src/context/lib.ts create mode 100644 frontend/packages/signless-transactions/src/context/metadata-provider.tsx create mode 100644 frontend/packages/signless-transactions/src/context/sails-provider.tsx rename frontend/packages/signless-transactions/src/hooks/{use-create-session.ts => use-create-base-session.ts} (52%) create mode 100644 frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts create mode 100644 frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts diff --git a/frontend/apps/battleship-zk/src/app/consts.ts b/frontend/apps/battleship-zk/src/app/consts.ts index 63bdacb89..b98fad98e 100644 --- a/frontend/apps/battleship-zk/src/app/consts.ts +++ b/frontend/apps/battleship-zk/src/app/consts.ts @@ -15,4 +15,4 @@ export const ROUTES = { NOTFOUND: '*', }; -export const SIGNLESS_ALLOWED_ACTIONS = ['StartGame', 'Turn']; +export const SIGNLESS_ALLOWED_ACTIONS = ['playSingleGame', 'playMultipleGame']; diff --git a/frontend/apps/battleship-zk/src/app/hocs/index.tsx b/frontend/apps/battleship-zk/src/app/hocs/index.tsx index a725e270d..779f161a0 100644 --- a/frontend/apps/battleship-zk/src/app/hocs/index.tsx +++ b/frontend/apps/battleship-zk/src/app/hocs/index.tsx @@ -16,6 +16,7 @@ import { import { ADDRESS } from '@/app/consts'; import { Alert, alertStyles } from '@/components/ui/alert'; import { QueryProvider } from './query-provider'; +import { useProgram } from '../utils/sails'; function ApiProvider({ children }: ProviderProps) { return {children}; @@ -40,13 +41,15 @@ function GaslessTransactionsProvider({ children }: ProviderProps) { ); } -// function SignlessTransactionsProvider({ children }: ProviderProps) { -// return ( -// -// {children} -// -// ); -// } +function SignlessTransactionsProvider({ children }: ProviderProps) { + const program = useProgram(); + + return ( + + {children} + + ); +} const providers = [ BrowserRouter, @@ -55,7 +58,7 @@ const providers = [ AlertProvider, QueryProvider, GaslessTransactionsProvider, - // SignlessTransactionsProvider, + SignlessTransactionsProvider, EzTransactionsProvider, ]; diff --git a/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts b/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts index d3b9e29f7..c46f30a5d 100644 --- a/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts +++ b/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts @@ -1,34 +1,72 @@ import { useEzTransactions } from '@dapps-frontend/ez-transactions'; -import { UsePrepareProgramTransactionParameters, useAccount, usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { useAccount } from '@gear-js/react-hooks'; import { web3FromSource } from '@polkadot/extension-dapp'; import { TransactionBuilder } from 'sails-js'; import { useProgram } from './sails'; +const usePrepareEzTransactionParams = () => { + const gasLimit = 250_000_000_000n; + const program = useProgram(); + const { account } = useAccount(); + const { signless, gasless } = useEzTransactions(); + const { pair, voucher } = signless; + + const prepareEzTransactionParams = async () => { + if (!program) throw new Error('program does not found'); + if (!account) throw new Error('Account not found'); + const sessionForAccount = pair ? account.decodedAddress : null; + + let voucherId = voucher?.id || gasless.voucherId; + if (account && gasless.isEnabled && !gasless.voucherId && !signless.isActive) { + voucherId = await gasless.requestVoucher(account.address); + } + + const injector = await web3FromSource(account.meta.source); + + return { + sessionForAccount, + account: pair + ? { addressOrPair: pair } + : { addressOrPair: account.decodedAddress, signerOptions: { signer: injector.signer } }, + voucherId, + gasLimit, + }; + }; + + return { prepareEzTransactionParams }; +}; + const useMakeTransaction = () => { + const gasLimit = 250_000_000_000n; const { account } = useAccount(); const { gasless, signless } = useEzTransactions(); + const { pair, voucher } = signless; return async (transactrionBuilder: TransactionBuilder) => { if (!account?.decodedAddress) { throw new Error('No account found!'); } - let { voucherId } = gasless; + let voucherId = voucher?.id || gasless.voucherId; if (account && gasless.isEnabled && !gasless.voucherId && !signless.isActive) { voucherId = await gasless.requestVoucher(account.address); } const injector = await web3FromSource(account.meta.source); - const transaction = transactrionBuilder.withAccount(account.address, { signer: injector.signer }); + if (pair) { + transactrionBuilder.withAccount(pair); + } else { + transactrionBuilder.withAccount(account.decodedAddress, { signer: injector.signer }); + } if (voucherId) { - transaction.withVoucher(voucherId); + transactrionBuilder.withVoucher(voucherId); } - return transaction; + return await transactrionBuilder.withGas(gasLimit); }; }; -export { useMakeTransaction }; +export { usePrepareEzTransactionParams, useMakeTransaction }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts index 7d62b5aef..38cfeb4d7 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts @@ -1,17 +1,21 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useCancelGameMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'cancelGame', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const cancelGameMessage = async () => { - if (!program) throw new Error('program does not found'); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ args: [sessionForAccount], ...params }); - const transaction = await makeTransaction(program.multiple.cancelGame(null)); - - return await transaction.withGas(gasLimit); + return transaction; }; return { cancelGameMessage }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts index 985431072..a16008673 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts @@ -1,17 +1,23 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useCreateGameMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'createGame', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const createGameMessage = async (name: string) => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.multiple.createGame(name, null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [name, sessionForAccount], + ...params, + }); + return transaction; }; return { createGameMessage }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts index e315c777f..b6022b526 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts @@ -1,17 +1,23 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useDeleteGameMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'deletePlayer', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const deletePlayerMessage = async (playerId: string) => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.multiple.deletePlayer(playerId, null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [playerId, sessionForAccount], + ...params, + }); + return transaction; }; return { deletePlayerMessage }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts index bcaa74e55..8c9e58c58 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts @@ -1,17 +1,23 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useJoinGameMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'joinGame', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const joinGameMessage = async (game_id: string, name: string) => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.multiple.joinGame(game_id, name, null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [game_id, name, sessionForAccount], + ...params, + }); + return transaction; }; return { joinGameMessage }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts index 8d67236ae..f1a7767b2 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts @@ -1,17 +1,20 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useLeaveGameMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'leaveGame', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const leaveGameMessage = async () => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.multiple.leaveGame(null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ args: [sessionForAccount], ...params }); + return transaction; }; return { leaveGameMessage }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts index 270a56959..969163dfb 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts @@ -1,11 +1,16 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; import { VerificationVariables } from '@/app/utils/sails/lib/lib'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useMakeMoveMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'makeMove', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const makeMoveMessage = async ( step: number | null, @@ -13,11 +18,12 @@ export const useMakeMoveMessage = () => { game_id?: string, ) => { if (!game_id) throw new Error('game_id does not found'); - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.multiple.makeMove(game_id, verify_variables, step, null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [game_id, verify_variables, step, sessionForAccount], + ...params, + }); + return transaction; }; return { makeMoveMessage }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts index 99fe27147..38f94e549 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts @@ -1,18 +1,24 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; import { ProofBytes, PublicStartInput } from '@/app/utils/sails/lib/lib'; export const useVerifyPlacementMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'multiple', + functionName: 'verifyPlacement', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const verifyPlacementMessage = async (proof: ProofBytes, public_input: PublicStartInput, game_id: string) => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.multiple.verifyPlacement(proof, public_input, null, game_id)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [proof, public_input, sessionForAccount, game_id], + ...params, + }); + return transaction; }; return { verifyPlacementMessage }; diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts index b1aef9e0f..9a0d363f2 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts +++ b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts @@ -1,18 +1,24 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; import { VerificationVariables } from '@/app/utils/sails/lib/lib'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; export const useMakeMoveMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'single', + functionName: 'makeMove', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const makeMoveMessage = async (step: number | null, verificationVariables: VerificationVariables | null) => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.single.makeMove(step, verificationVariables, null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [step, verificationVariables, sessionForAccount], + ...params, + }); + return transaction; }; return { makeMoveMessage }; diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts index 089f5419f..f5f69f9c3 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts @@ -1,18 +1,24 @@ +import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { useMakeTransaction } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; import { ProofBytes, PublicStartInput } from '@/app/utils/sails/lib/lib'; export const useStartGameMessage = () => { - const gasLimit = 250_000_000_000n; - const makeTransaction = useMakeTransaction(); const program = useProgram(); + const { prepareTransactionAsync } = usePrepareProgramTransaction({ + program, + serviceName: 'single', + functionName: 'startSingleGame', + }); + const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const startGameMessage = async (proof: ProofBytes, public_input: PublicStartInput) => { - if (!program) throw new Error('program does not found'); - - const transaction = await makeTransaction(program.single.startSingleGame(proof, public_input, null)); - - return await transaction.withGas(gasLimit); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { transaction } = await prepareTransactionAsync({ + args: [proof, public_input, sessionForAccount], + ...params, + }); + return transaction; }; return { startGameMessage }; diff --git a/frontend/apps/racing-car-game/src/utils.ts b/frontend/apps/racing-car-game/src/utils.ts index 346758737..abb632347 100644 --- a/frontend/apps/racing-car-game/src/utils.ts +++ b/frontend/apps/racing-car-game/src/utils.ts @@ -1,5 +1,5 @@ import { ProgramMetadata } from '@gear-js/api'; -import { SignlessTransactionsProviderProps } from '@dapps-frontend/signless-transactions'; +import { SignlessTransactionsMetadataProviderProps } from '@dapps-frontend/signless-transactions'; import { AlertContainerFactory } from '@gear-js/react-hooks/dist/esm/types'; import { Bytes } from '@polkadot/types'; import { Codec } from '@polkadot/types/types'; @@ -104,7 +104,7 @@ export const logger = (message: unknown | unknown[]) => { /** * Get first element of tuple type * */ -export const createSignatureType: SignlessTransactionsProviderProps['createSignatureType'] = ( +export const createSignatureType: SignlessTransactionsMetadataProviderProps['createSignatureType'] = ( metadata, payloadToSign, ) => { diff --git a/frontend/packages/signless-transactions/src/context/context.ts b/frontend/packages/signless-transactions/src/context/context.ts new file mode 100644 index 000000000..7e2b74388 --- /dev/null +++ b/frontend/packages/signless-transactions/src/context/context.ts @@ -0,0 +1,7 @@ +import { createContext } from 'react'; +import { SignlessContext } from './types'; +import { DEFAULT_SIGNLESS_CONTEXT } from './consts'; + +const SignlessTransactionsContext = createContext(DEFAULT_SIGNLESS_CONTEXT); + +export { SignlessTransactionsContext }; diff --git a/frontend/packages/signless-transactions/src/context/hooks.ts b/frontend/packages/signless-transactions/src/context/hooks.ts index 5973b83a8..6417b5da8 100644 --- a/frontend/packages/signless-transactions/src/context/hooks.ts +++ b/frontend/packages/signless-transactions/src/context/hooks.ts @@ -1,10 +1,22 @@ import { HexString, ProgramMetadata, decodeAddress } from '@gear-js/api'; -import { getTypedEntries, useAccount, useReadFullState, useVouchers } from '@gear-js/react-hooks'; -import { useMemo } from 'react'; +import { + getTypedEntries, + useAccount, + useBalance, + useProgramQuery, + useReadFullState, + useVouchers, +} from '@gear-js/react-hooks'; +import { useMemo, useEffect, useState } from 'react'; -import { State } from './types'; +import { KeyringPair, KeyringPair$Json } from '@polkadot/keyring/types'; -function useSession(programId: HexString, metadata: ProgramMetadata | undefined) { +import { SIGNLESS_STORAGE_KEY } from './consts'; +import { getUnlockedPair } from '../utils'; +import { getStorage } from './utils'; +import { BaseProgram, Session, State } from './types'; + +function useMetadataSession(programId: HexString, metadata: ProgramMetadata | undefined) { const { account } = useAccount(); const payload = useMemo(() => ({ SessionForTheAccount: account?.decodedAddress }), [account]); @@ -12,8 +24,32 @@ function useSession(programId: HexString, metadata: ProgramMetadata | undefined) const session = state?.SessionForTheAccount; const isSessionReady = session !== undefined; + const isSessionActive = Boolean(session); + + return { session, isSessionReady, isSessionActive }; +} + +function useSailsSession(program: BaseProgram) { + const { account } = useAccount(); + const { data: responseSession } = useProgramQuery({ + program, + serviceName: 'session', + functionName: 'sessionForTheAccount', + args: [account?.decodedAddress || ''], + watch: true, + }); + + const isSessionReady = responseSession !== undefined; + const isSessionActive = Boolean(responseSession); + + if (!responseSession) { + return { session: responseSession, isSessionReady, isSessionActive }; + } - return { session, isSessionReady }; + const { key, expires, allowed_actions } = responseSession; + const session = { key: key as HexString, expires: String(expires), allowedActions: allowed_actions }; + + return { session, isSessionReady, isSessionActive }; } function useLatestVoucher(programId: HexString, address: string | undefined) { @@ -33,4 +69,79 @@ function useLatestVoucher(programId: HexString, address: string | undefined) { return latestVoucher; } -export { useSession, useLatestVoucher }; +function usePair(programId: HexString, session?: Session | null) { + const { account } = useAccount(); + const [pair, setPair] = useState(); + const voucher = useLatestVoucher(programId, pair?.address); + const { balance } = useBalance(voucher?.id); + const voucherBalance = balance ? balance.toNumber() : 0; + + // there's probably a better way to handle storage voucher, since we may not need it in a context + const [storagePair, setStoragePair] = useState(account ? getStorage()[account.address] : undefined); + const storageVoucher = useLatestVoucher(programId, storagePair?.address); + const { balance: _storageVoucherBalance } = useBalance(storageVoucher?.id); + const storageVoucherBalance = _storageVoucherBalance ? _storageVoucherBalance.toNumber() : 0; + + const [isLoading, setIsLoading] = useState(false); + const isActive = Boolean(pair); + + const unlockPair = (password: string) => { + if (!storagePair) throw new Error('Pair not found'); + + const result = getUnlockedPair(storagePair, password); + + setPair(result); + }; + + const setPairToStorage = (value: KeyringPair$Json | undefined) => { + if (!account) throw new Error('No account address'); + + const storage = { ...getStorage(), [account.address]: value }; + + localStorage.setItem(SIGNLESS_STORAGE_KEY, JSON.stringify(storage)); + setStoragePair(value); + }; + + useEffect(() => { + if (!account) return setStoragePair(undefined); + + setStoragePair(getStorage()[account.address]); + }, [account]); + + const savePair = (value: KeyringPair, password: string) => { + setPairToStorage(value.toJson(password)); + setPair(value); + }; + + const deletePair = () => { + setPairToStorage(undefined); + setPair(undefined); + }; + + useEffect(() => { + if (session) return; + + setPair(undefined); + }, [session]); + + useEffect(() => { + setPair(undefined); + }, [account]); + + return { + pair, + storagePair, + savePair, + deletePair, + unlockPair, + voucherBalance, + voucher, + isLoading, + setIsLoading, + isActive, + storageVoucher, + storageVoucherBalance, + }; +} + +export { useMetadataSession, useSailsSession, useLatestVoucher, usePair }; diff --git a/frontend/packages/signless-transactions/src/context/index.tsx b/frontend/packages/signless-transactions/src/context/index.tsx index bd4df910a..4adfff55d 100644 --- a/frontend/packages/signless-transactions/src/context/index.tsx +++ b/frontend/packages/signless-transactions/src/context/index.tsx @@ -1,124 +1,29 @@ -import { HexString, ProgramMetadata } from '@gear-js/api'; -import { useAccount, useBalance } from '@gear-js/react-hooks'; -import { KeyringPair, KeyringPair$Json } from '@polkadot/keyring/types'; -import { ReactNode, createContext, useContext, useEffect, useState } from 'react'; - -import { useProgramMetadata } from '@dapps-frontend/hooks'; - -import { Session, useCreateSession } from '../hooks'; -import { DEFAULT_SIGNLESS_CONTEXT, SIGNLESS_STORAGE_KEY } from './consts'; -import { SignlessContext } from './types'; -import { useSession, useLatestVoucher } from './hooks'; -import { getUnlockedPair } from '../utils'; -import { getStorage } from './utils'; - -const SignlessTransactionsContext = createContext(DEFAULT_SIGNLESS_CONTEXT); -const { Provider } = SignlessTransactionsContext; - -type SignlessTransactionsProviderProps = { - programId: HexString; - metadataSource: string; - children: ReactNode; - /** - * createSignatureType param is used when metadata.types.others.output has multiple types (e.g. tuple) to get the actual type for SignatureData - */ - createSignatureType?: (metadata: ProgramMetadata, payloadToSig: Session) => `0x${string}`; -}; - -function SignlessTransactionsProvider({ - metadataSource, - programId, - children, - createSignatureType, -}: SignlessTransactionsProviderProps) { - const { account } = useAccount(); - - const metadata = useProgramMetadata(metadataSource); - const { session, isSessionReady } = useSession(programId, metadata); - const { createSession, deleteSession } = useCreateSession(programId, metadata, createSignatureType); - - const [pair, setPair] = useState(); - const voucher = useLatestVoucher(programId, pair?.address); - const { balance } = useBalance(voucher?.id); - const voucherBalance = balance ? balance.toNumber() : 0; - - // there's probably a better way to handle storage voucher, since we may not need it in a context - const [storagePair, setStoragePair] = useState(account ? getStorage()[account.address] : undefined); - const storageVoucher = useLatestVoucher(programId, storagePair?.address); - const { balance: _storageVoucherBalance } = useBalance(storageVoucher?.id); - const storageVoucherBalance = _storageVoucherBalance ? _storageVoucherBalance.toNumber() : 0; - - const [isLoading, setIsLoading] = useState(false); - const isActive = Boolean(pair); - const isSessionActive = Boolean(session); - - const unlockPair = (password: string) => { - if (!storagePair) throw new Error('Pair not found'); - - const result = getUnlockedPair(storagePair, password); - - setPair(result); - }; - - const setPairToStorage = (value: KeyringPair$Json | undefined) => { - if (!account) throw new Error('No account address'); - - const storage = { ...getStorage(), [account.address]: value }; - - localStorage.setItem(SIGNLESS_STORAGE_KEY, JSON.stringify(storage)); - setStoragePair(value); - }; - - useEffect(() => { - if (!account) return setStoragePair(undefined); - - setStoragePair(getStorage()[account.address]); - }, [account]); - - const savePair = (value: KeyringPair, password: string) => { - setPairToStorage(value.toJson(password)); - setPair(value); - }; - - const deletePair = () => { - setPairToStorage(undefined); - setPair(undefined); - }; - - useEffect(() => { - if (session) return; - - setPair(undefined); - }, [session]); - - useEffect(() => { - setPair(undefined); - }, [account]); - - const value = { - pair, - storagePair, - savePair, - deletePair, - unlockPair, - session, - isSessionReady, - voucherBalance, - createSession, - deleteSession, - voucher, - isLoading, - setIsLoading, - isActive, - isSessionActive, - storageVoucher, - storageVoucherBalance, - }; - - return {children}; +import { useContext, ReactElement } from 'react'; + +import { DEFAULT_SIGNLESS_CONTEXT } from './consts'; +import { SignlessTransactionsContext } from './context'; +import { BaseProgram, SignlessContext } from './types'; +import { SignlessTransactionsMetadataProvider, SignlessTransactionsMetadataProviderProps } from './metadata-provider'; +import { SignlessTransactionsSailsProvider, SignlessTransactionsSailsProviderProps } from './sails-provider'; + +function SignlessTransactionsProvider(props: SignlessTransactionsMetadataProviderProps): ReactElement; +function SignlessTransactionsProvider( + props: SignlessTransactionsSailsProviderProps, +): ReactElement; + +function SignlessTransactionsProvider( + props: SignlessTransactionsMetadataProviderProps | SignlessTransactionsSailsProviderProps, +) { + if ('metadataSource' in props) { + return SignlessTransactionsMetadataProvider(props); + } else if ('program' in props) { + return ; + } else { + throw new Error('Invalid SignlessTransactionsProvider props'); + } } const useSignlessTransactions = () => useContext(SignlessTransactionsContext); export { SignlessTransactionsProvider, useSignlessTransactions, DEFAULT_SIGNLESS_CONTEXT }; -export type { SignlessContext, SignlessTransactionsProviderProps }; +export type { SignlessContext, SignlessTransactionsMetadataProviderProps, SignlessTransactionsSailsProviderProps }; diff --git a/frontend/packages/signless-transactions/src/context/lib.ts b/frontend/packages/signless-transactions/src/context/lib.ts new file mode 100644 index 000000000..1d05d1b1c --- /dev/null +++ b/frontend/packages/signless-transactions/src/context/lib.ts @@ -0,0 +1,1335 @@ +import { TransactionBuilder, getServiceNamePrefix, getFnNamePrefix, ZERO_ADDRESS } from 'sails-js'; +import { GearApi, decodeAddress } from '@gear-js/api'; +import { TypeRegistry } from '@polkadot/types'; + +export type ActorId = string; + +export interface VerifyingKeyBytes { + alpha_g1_beta_g2: `0x${string}`; + gamma_g2_neg_pc: `0x${string}`; + delta_g2_neg_pc: `0x${string}`; + ic: Array<`0x${string}`>; +} + +export interface Configuration { + gas_for_delete_single_game: number | string | bigint; + gas_for_delete_multiple_game: number | string | bigint; + gas_for_check_time: number | string | bigint; + delay_for_delete_single_game: number; + delay_for_delete_multiple_game: number; + delay_for_check_time: number; +} + +export interface VerificationVariables { + proof_bytes: ProofBytes; + public_input: PublicMoveInput; +} + +export interface ProofBytes { + a: `0x${string}`; + b: `0x${string}`; + c: `0x${string}`; +} + +export interface PublicMoveInput { + out: number; + hit: number; + hash: `0x${string}`; +} + +export interface PublicStartInput { + hash: `0x${string}`; +} + +export interface MultipleGameState { + admin: ActorId; + participants_data: Array<[ActorId, ParticipantInfo]>; + create_time: number | string | bigint; + start_time: number | string | bigint | null; + last_move_time: number | string | bigint; + status: Status; + bid: number | string | bigint; +} + +export interface ParticipantInfo { + name: string; + board: Array; + ship_hash: `0x${string}`; + total_shots: number; + succesfull_shots: number; +} + +export type Entity = 'Empty' | 'Unknown' | 'Occupied' | 'Ship' | 'Boom' | 'BoomShip' | 'DeadShip'; + +export type Status = + | { registration: null } + | { verificationPlacement: ActorId | null } + | { pendingVerificationOfTheMove: [ActorId, number] } + | { turn: ActorId }; + +export type MultipleUtilsStepResult = 'Missed' | 'Injured' | 'Killed'; + +export type ActionsForSession = 'playSingleGame' | 'playMultipleGame'; + +export interface Session { + key: ActorId; + expires: number | string | bigint; + allowed_actions: Array; +} + +export interface SingleGame { + player_board: Array; + ship_hash: `0x${string}`; + bot_ships: Ships; + start_time: number | string | bigint; + total_shots: number; + succesfull_shots: number; + last_move_time: number | string | bigint; + verification_requirement: number | null; +} + +export interface Ships { + ship_1: `0x${string}`; + ship_2: `0x${string}`; + ship_3: `0x${string}`; + ship_4: `0x${string}`; +} + +export interface SingleGameState { + player_board: Array; + ship_hash: `0x${string}`; + start_time: number | string | bigint; + total_shots: number; + succesfull_shots: number; + last_move_time: number | string | bigint; + verification_requirement: number | null; +} + +export type BattleshipParticipants = 'Player' | 'Bot'; + +export type SingleUtilsStepResult = 'Missed' | 'Injured' | 'Killed'; + +export class Program { + public readonly registry: TypeRegistry; + public readonly admin: Admin; + public readonly multiple: Multiple; + public readonly session: Session; + public readonly single: Single; + + constructor(public api: GearApi, public programId?: `0x${string}`) { + const types: Record = { + VerifyingKeyBytes: { + alpha_g1_beta_g2: 'Vec', + gamma_g2_neg_pc: 'Vec', + delta_g2_neg_pc: 'Vec', + ic: 'Vec>', + }, + Configuration: { + gas_for_delete_single_game: 'u64', + gas_for_delete_multiple_game: 'u64', + gas_for_check_time: 'u64', + delay_for_delete_single_game: 'u32', + delay_for_delete_multiple_game: 'u32', + delay_for_check_time: 'u32', + }, + VerificationVariables: { proof_bytes: 'ProofBytes', public_input: 'PublicMoveInput' }, + ProofBytes: { a: 'Vec', b: 'Vec', c: 'Vec' }, + PublicMoveInput: { out: 'u8', hit: 'u8', hash: 'Vec' }, + PublicStartInput: { hash: 'Vec' }, + MultipleGameState: { + admin: '[u8;32]', + participants_data: 'Vec<([u8;32], ParticipantInfo)>', + create_time: 'u64', + start_time: 'Option', + last_move_time: 'u64', + status: 'Status', + bid: 'u128', + }, + ParticipantInfo: { + name: 'String', + board: 'Vec', + ship_hash: 'Vec', + total_shots: 'u8', + succesfull_shots: 'u8', + }, + Entity: { _enum: ['Empty', 'Unknown', 'Occupied', 'Ship', 'Boom', 'BoomShip', 'DeadShip'] }, + Status: { + _enum: { + Registration: 'Null', + VerificationPlacement: 'Option<[u8;32]>', + PendingVerificationOfTheMove: '([u8;32], u8)', + Turn: '[u8;32]', + }, + }, + MultipleUtilsStepResult: { _enum: ['Missed', 'Injured', 'Killed'] }, + ActionsForSession: { _enum: ['PlaySingleGame', 'PlayMultipleGame'] }, + Session: { key: '[u8;32]', expires: 'u64', allowed_actions: 'Vec' }, + SingleGame: { + player_board: 'Vec', + ship_hash: 'Vec', + bot_ships: 'Ships', + start_time: 'u64', + total_shots: 'u8', + succesfull_shots: 'u8', + last_move_time: 'u64', + verification_requirement: 'Option', + }, + Ships: { ship_1: 'Vec', ship_2: 'Vec', ship_3: 'Vec', ship_4: 'Vec' }, + SingleGameState: { + player_board: 'Vec', + ship_hash: 'Vec', + start_time: 'u64', + total_shots: 'u8', + succesfull_shots: 'u8', + last_move_time: 'u64', + verification_requirement: 'Option', + }, + BattleshipParticipants: { _enum: ['Player', 'Bot'] }, + SingleUtilsStepResult: { _enum: ['Missed', 'Injured', 'Killed'] }, + }; + + this.registry = new TypeRegistry(); + this.registry.setKnownTypes({ types }); + this.registry.register(types); + + this.admin = new Admin(this); + this.multiple = new Multiple(this); + this.session = new Session(this); + this.single = new Single(this); + } + + newCtorFromCode( + code: Uint8Array | Buffer, + builtin_bls381: ActorId, + verification_key_for_start: VerifyingKeyBytes, + verification_key_for_move: VerifyingKeyBytes, + config: Configuration, + ): TransactionBuilder { + const builder = new TransactionBuilder( + this.api, + this.registry, + 'upload_program', + ['New', builtin_bls381, verification_key_for_start, verification_key_for_move, config], + '(String, [u8;32], VerifyingKeyBytes, VerifyingKeyBytes, Configuration)', + 'String', + code, + ); + + this.programId = builder.programId; + return builder; + } + + newCtorFromCodeId( + codeId: `0x${string}`, + builtin_bls381: ActorId, + verification_key_for_start: VerifyingKeyBytes, + verification_key_for_move: VerifyingKeyBytes, + config: Configuration, + ) { + const builder = new TransactionBuilder( + this.api, + this.registry, + 'create_program', + ['New', builtin_bls381, verification_key_for_start, verification_key_for_move, config], + '(String, [u8;32], VerifyingKeyBytes, VerifyingKeyBytes, Configuration)', + 'String', + codeId, + ); + + this.programId = builder.programId; + return builder; + } +} + +export class Admin { + constructor(private _program: Program) {} + + public changeAdmin(new_admin: ActorId): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'ChangeAdmin', new_admin], + '(String, String, [u8;32])', + 'Null', + this._program.programId, + ); + } + + public changeBuiltinAddress(new_builtin_address: ActorId): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'ChangeBuiltinAddress', new_builtin_address], + '(String, String, [u8;32])', + 'Null', + this._program.programId, + ); + } + + public changeConfiguration(configuration: Configuration): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'ChangeConfiguration', configuration], + '(String, String, Configuration)', + 'Null', + this._program.programId, + ); + } + + public changeVerificationKey( + new_vk_for_start: VerifyingKeyBytes | null, + new_vk_for_move: VerifyingKeyBytes | null, + ): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'ChangeVerificationKey', new_vk_for_start, new_vk_for_move], + '(String, String, Option, Option)', + 'Null', + this._program.programId, + ); + } + + public deleteMultipleGame(game_id: ActorId): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'DeleteMultipleGame', game_id], + '(String, String, [u8;32])', + 'Null', + this._program.programId, + ); + } + + public deleteMultipleGamesByTime(time: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'DeleteMultipleGamesByTime', time], + '(String, String, u64)', + 'Null', + this._program.programId, + ); + } + + public deleteMultipleGamesInBatches(divider: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'DeleteMultipleGamesInBatches', divider], + '(String, String, u64)', + 'Null', + this._program.programId, + ); + } + + public deleteSingleGame(player_address: ActorId): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'DeleteSingleGame', player_address], + '(String, String, [u8;32])', + 'Null', + this._program.programId, + ); + } + + public deleteSingleGames(time: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'DeleteSingleGames', time], + '(String, String, u64)', + 'Null', + this._program.programId, + ); + } + + public kill(inheritor: ActorId): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Admin', 'Kill', inheritor], + '(String, String, [u8;32])', + 'Null', + this._program.programId, + ); + } + + public async admin( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry.createType('(String, String)', ['Admin', 'Admin']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, [u8;32])', reply.payload); + return result[2].toJSON() as unknown as ActorId; + } + + public async builtin( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry.createType('(String, String)', ['Admin', 'Builtin']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, [u8;32])', reply.payload); + return result[2].toJSON() as unknown as ActorId; + } + + public async configuration( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry.createType('(String, String)', ['Admin', 'Configuration']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Configuration)', reply.payload); + return result[2].toJSON() as unknown as Configuration; + } + + public async verificationKey( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise<[VerifyingKeyBytes, VerifyingKeyBytes]> { + const payload = this._program.registry.createType('(String, String)', ['Admin', 'VerificationKey']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType( + '(String, String, (VerifyingKeyBytes, VerifyingKeyBytes))', + reply.payload, + ); + return result[2].toJSON() as unknown as [VerifyingKeyBytes, VerifyingKeyBytes]; + } + + public subscribeToGameDeletedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'GameDeleted') { + callback(null); + } + }); + } + + public subscribeToGamesDeletedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'GamesDeleted') { + callback(null); + } + }); + } + + public subscribeToAdminChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'AdminChanged') { + callback(null); + } + }); + } + + public subscribeToBuiltinAddressChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'BuiltinAddressChanged') { + callback(null); + } + }); + } + + public subscribeToVerificationKeyChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'VerificationKeyChanged') { + callback(null); + } + }); + } + + public subscribeToConfigurationChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'ConfigurationChanged') { + callback(null); + } + }); + } + + public subscribeToKilledEvent(callback: (data: { inheritor: ActorId }) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'Killed') { + callback( + this._program.registry + .createType('(String, String, {"inheritor":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { inheritor: ActorId }, + ); + } + }); + } +} + +export class Multiple { + constructor(private _program: Program) {} + + public cancelGame(session_for_account: ActorId | null): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'CancelGame', session_for_account], + '(String, String, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public checkOutTiming(game_id: ActorId, check_time: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'CheckOutTiming', game_id, check_time], + '(String, String, [u8;32], u64)', + 'Null', + this._program.programId, + ); + } + + public createGame(name: string, session_for_account: ActorId | null): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'CreateGame', name, session_for_account], + '(String, String, String, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public deleteGame(game_id: ActorId, create_time: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'DeleteGame', game_id, create_time], + '(String, String, [u8;32], u64)', + 'Null', + this._program.programId, + ); + } + + public deletePlayer(removable_player: ActorId, session_for_account: ActorId | null): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'DeletePlayer', removable_player, session_for_account], + '(String, String, [u8;32], Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public joinGame(game_id: ActorId, name: string, session_for_account: ActorId | null): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'JoinGame', game_id, name, session_for_account], + '(String, String, [u8;32], String, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public leaveGame(session_for_account: ActorId | null): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'LeaveGame', session_for_account], + '(String, String, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public makeMove( + game_id: ActorId, + verify_variables: VerificationVariables | null, + step: number | null, + session_for_account: ActorId | null, + ): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'MakeMove', game_id, verify_variables, step, session_for_account], + '(String, String, [u8;32], Option, Option, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public verifyPlacement( + proof: ProofBytes, + public_input: PublicStartInput, + session_for_account: ActorId | null, + game_id: ActorId, + ): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Multiple', 'VerifyPlacement', proof, public_input, session_for_account, game_id], + '(String, String, ProofBytes, PublicStartInput, Option<[u8;32]>, [u8;32])', + 'Null', + this._program.programId, + ); + } + + public async game( + player_id: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Multiple', 'Game', player_id]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as MultipleGameState | null; + } + + public async games( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise> { + const payload = this._program.registry.createType('(String, String)', ['Multiple', 'Games']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType( + '(String, String, Vec<([u8;32], MultipleGameState)>)', + reply.payload, + ); + return result[2].toJSON() as unknown as Array<[ActorId, MultipleGameState]>; + } + + public async gamesPairs( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise> { + const payload = this._program.registry.createType('(String, String)', ['Multiple', 'GamesPairs']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Vec<([u8;32], [u8;32])>)', reply.payload); + return result[2].toJSON() as unknown as Array<[ActorId, ActorId]>; + } + + public async getRemainingTime( + player_id: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Multiple', 'GetRemainingTime', player_id]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as number | string | bigint | null; + } + + public subscribeToGameCreatedEvent( + callback: (data: { player_id: ActorId }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameCreated') { + callback( + this._program.registry + .createType('(String, String, {"player_id":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { player_id: ActorId }, + ); + } + }); + } + + public subscribeToJoinedTheGameEvent( + callback: (data: { player_id: ActorId; game_id: ActorId }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'JoinedTheGame') { + callback( + this._program.registry + .createType('(String, String, {"player_id":"[u8;32]","game_id":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { player_id: ActorId; game_id: ActorId }, + ); + } + }); + } + + public subscribeToPlacementVerifiedEvent( + callback: (data: { admin: ActorId }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'PlacementVerified') { + callback( + this._program.registry + .createType('(String, String, {"admin":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { admin: ActorId }, + ); + } + }); + } + + public subscribeToGameCanceledEvent( + callback: (data: { game_id: ActorId }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameCanceled') { + callback( + this._program.registry + .createType('(String, String, {"game_id":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { game_id: ActorId }, + ); + } + }); + } + + public subscribeToGameLeftEvent(callback: (data: { game_id: ActorId }) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameLeft') { + callback( + this._program.registry + .createType('(String, String, {"game_id":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { game_id: ActorId }, + ); + } + }); + } + + public subscribeToMoveMadeEvent( + callback: (data: { + game_id: ActorId; + step: number | null; + verified_result: [number, MultipleUtilsStepResult] | null; + turn: ActorId; + }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'MoveMade') { + callback( + this._program.registry + .createType( + '(String, String, {"game_id":"[u8;32]","step":"Option","verified_result":"Option<(u8, MultipleUtilsStepResult)>","turn":"[u8;32]"})', + message.payload, + )[2] + .toJSON() as unknown as { + game_id: ActorId; + step: number | null; + verified_result: [number, MultipleUtilsStepResult] | null; + turn: ActorId; + }, + ); + } + }); + } + + public subscribeToEndGameEvent( + callback: (data: { + admin: ActorId; + winner: ActorId; + total_time: number | string | bigint; + participants_info: Array<[ActorId, ParticipantInfo]>; + last_hit: number | null; + }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'EndGame') { + callback( + this._program.registry + .createType( + '(String, String, {"admin":"[u8;32]","winner":"[u8;32]","total_time":"u64","participants_info":"Vec<([u8;32], ParticipantInfo)>","last_hit":"Option"})', + message.payload, + )[2] + .toJSON() as unknown as { + admin: ActorId; + winner: ActorId; + total_time: number | string | bigint; + participants_info: Array<[ActorId, ParticipantInfo]>; + last_hit: number | null; + }, + ); + } + }); + } + + public subscribeToGameDeletedEvent( + callback: (data: { game_id: ActorId }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameDeleted') { + callback( + this._program.registry + .createType('(String, String, {"game_id":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { game_id: ActorId }, + ); + } + }); + } + + public subscribeToPlayerDeletedEvent( + callback: (data: { game_id: ActorId; removable_player: ActorId }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'PlayerDeleted') { + callback( + this._program.registry + .createType('(String, String, {"game_id":"[u8;32]","removable_player":"[u8;32]"})', message.payload)[2] + .toJSON() as unknown as { game_id: ActorId; removable_player: ActorId }, + ); + } + }); + } +} + +export class Session { + constructor(private _program: Program) {} + + public createSession( + key: ActorId, + duration: number | string | bigint, + allowed_actions: Array, + ): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Session', 'CreateSession', key, duration, allowed_actions], + '(String, String, [u8;32], u64, Vec)', + 'Null', + this._program.programId, + ); + } + + public deleteSession(): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Session', 'DeleteSession'], + '(String, String)', + 'Null', + this._program.programId, + ); + } + + public async sessionForTheAccount( + account: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Session', 'SessionForTheAccount', account]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as Session | null; + } + + public async sessions( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise> { + const payload = this._program.registry.createType('(String, String)', ['Session', 'Sessions']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Vec<([u8;32], Session)>)', reply.payload); + return result[2].toJSON() as unknown as Array<[ActorId, Session]>; + } + + public subscribeToSessionCreatedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Session' && getFnNamePrefix(payload) === 'SessionCreated') { + callback(null); + } + }); + } + + public subscribeToSessionDeletedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Session' && getFnNamePrefix(payload) === 'SessionDeleted') { + callback(null); + } + }); + } +} + +export class Single { + constructor(private _program: Program) {} + + public checkOutTiming(actor_id: ActorId, check_time: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Single', 'CheckOutTiming', actor_id, check_time], + '(String, String, [u8;32], u64)', + 'Null', + this._program.programId, + ); + } + + public deleteGame(player: ActorId, start_time: number | string | bigint): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Single', 'DeleteGame', player, start_time], + '(String, String, [u8;32], u64)', + 'Null', + this._program.programId, + ); + } + + public makeMove( + step: number | null, + verify_variables: VerificationVariables | null, + session_for_account: ActorId | null, + ): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Single', 'MakeMove', step, verify_variables, session_for_account], + '(String, String, Option, Option, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public startSingleGame( + proof: ProofBytes, + public_input: PublicStartInput, + session_for_account: ActorId | null, + ): TransactionBuilder { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder( + this._program.api, + this._program.registry, + 'send_message', + ['Single', 'StartSingleGame', proof, public_input, session_for_account], + '(String, String, ProofBytes, PublicStartInput, Option<[u8;32]>)', + 'Null', + this._program.programId, + ); + } + + public async game( + player_id: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Single', 'Game', player_id]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as SingleGame | null; + } + + public async games( + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise> { + const payload = this._program.registry.createType('(String, String)', ['Single', 'Games']).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType( + '(String, String, Vec<([u8;32], SingleGameState)>)', + reply.payload, + ); + return result[2].toJSON() as unknown as Array<[ActorId, SingleGameState]>; + } + + public async getRemainingTime( + player_id: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Single', 'GetRemainingTime', player_id]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as number | string | bigint | null; + } + + public async startTime( + player_id: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Single', 'StartTime', player_id]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as number | string | bigint | null; + } + + public async totalShots( + player_id: ActorId, + originAddress?: string, + value?: number | string | bigint, + atBlock?: `0x${string}`, + ): Promise { + const payload = this._program.registry + .createType('(String, String, [u8;32])', ['Single', 'TotalShots', player_id]) + .toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId!, + origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, + payload, + value: value || 0, + gasLimit: this._program.api.blockGasLimit.toBigInt(), + at: atBlock, + }); + if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as number | null; + } + + public subscribeToSessionCreatedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'SessionCreated') { + callback(null); + } + }); + } + + public subscribeToSingleGameStartedEvent(callback: (data: null) => void | Promise): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'SingleGameStarted') { + callback(null); + } + }); + } + + public subscribeToEndGameEvent( + callback: (data: { + player: ActorId; + winner: BattleshipParticipants; + time: number | string | bigint; + total_shots: number; + succesfull_shots: number; + last_hit: number | null; + }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'EndGame') { + callback( + this._program.registry + .createType( + '(String, String, {"player":"[u8;32]","winner":"BattleshipParticipants","time":"u64","total_shots":"u8","succesfull_shots":"u8","last_hit":"Option"})', + message.payload, + )[2] + .toJSON() as unknown as { + player: ActorId; + winner: BattleshipParticipants; + time: number | string | bigint; + total_shots: number; + succesfull_shots: number; + last_hit: number | null; + }, + ); + } + }); + } + + public subscribeToMoveMadeEvent( + callback: (data: { + player: ActorId; + step: number | null; + step_result: SingleUtilsStepResult | null; + bot_step: number | null; + }) => void | Promise, + ): Promise<() => void> { + return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { + if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { + return; + } + + const payload = message.payload.toHex(); + if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'MoveMade') { + callback( + this._program.registry + .createType( + '(String, String, {"player":"[u8;32]","step":"Option","step_result":"Option","bot_step":"Option"})', + message.payload, + )[2] + .toJSON() as unknown as { + player: ActorId; + step: number | null; + step_result: SingleUtilsStepResult | null; + bot_step: number | null; + }, + ); + } + }); + } +} diff --git a/frontend/packages/signless-transactions/src/context/metadata-provider.tsx b/frontend/packages/signless-transactions/src/context/metadata-provider.tsx new file mode 100644 index 000000000..d63d52c3b --- /dev/null +++ b/frontend/packages/signless-transactions/src/context/metadata-provider.tsx @@ -0,0 +1,44 @@ +import { HexString, ProgramMetadata } from '@gear-js/api'; +import { ReactNode } from 'react'; + +import { useProgramMetadata } from '@dapps-frontend/hooks'; + +import { Session, useCreateMetadataSession } from '../hooks'; +import { usePair, useMetadataSession } from './hooks'; +import { SignlessTransactionsContext } from './context'; + +type SignlessTransactionsMetadataProviderProps = { + programId: HexString; + metadataSource: string; + children: ReactNode; + /** + * createSignatureType param is used when metadata.types.others.output has multiple types (e.g. tuple) to get the actual type for SignatureData + */ + createSignatureType?: (metadata: ProgramMetadata, payloadToSig: Session) => `0x${string}`; +}; + +function SignlessTransactionsMetadataProvider({ + metadataSource, + programId, + children, + createSignatureType, +}: SignlessTransactionsMetadataProviderProps) { + const metadata = useProgramMetadata(metadataSource); + const { session, isSessionReady, isSessionActive } = useMetadataSession(programId, metadata); + const { createSession, deleteSession } = useCreateMetadataSession(programId, metadata, createSignatureType); + const pairData = usePair(programId, session); + + const value = { + ...pairData, + session, + isSessionReady, + createSession, + deleteSession, + isSessionActive, + }; + + return {children}; +} + +export { SignlessTransactionsMetadataProvider }; +export type { SignlessTransactionsMetadataProviderProps }; diff --git a/frontend/packages/signless-transactions/src/context/sails-provider.tsx b/frontend/packages/signless-transactions/src/context/sails-provider.tsx new file mode 100644 index 000000000..0b3c8619a --- /dev/null +++ b/frontend/packages/signless-transactions/src/context/sails-provider.tsx @@ -0,0 +1,36 @@ +import { HexString } from '@gear-js/api'; +import { ReactNode } from 'react'; + +import { useCreateSailsSession } from '@/hooks'; +import { usePair, useSailsSession } from './hooks'; +import { SignlessTransactionsContext } from './context'; +import { BaseProgram } from './types'; + +type SignlessTransactionsSailsProviderProps = { + programId: HexString; + children: ReactNode; + program: TProgram; +}; + +function SignlessTransactionsSailsProvider({ + programId, + children, + program, +}: SignlessTransactionsSailsProviderProps) { + const { session, isSessionReady, isSessionActive } = useSailsSession(program); + const { createSession, deleteSession } = useCreateSailsSession(programId, program); + const pairData = usePair(programId, session); + const value = { + ...pairData, + session, + isSessionReady, + createSession, + deleteSession, + isSessionActive, + }; + + return {children}; +} + +export { SignlessTransactionsSailsProvider }; +export type { SignlessTransactionsSailsProviderProps }; diff --git a/frontend/packages/signless-transactions/src/context/types.ts b/frontend/packages/signless-transactions/src/context/types.ts index 3a2363098..c74da9d01 100644 --- a/frontend/packages/signless-transactions/src/context/types.ts +++ b/frontend/packages/signless-transactions/src/context/types.ts @@ -1,8 +1,9 @@ +import { TransactionBuilder } from 'sails-js'; +import { IVoucherDetails } from '@gear-js/api'; import { HexString } from '@polkadot/util/types'; import { KeyringPair$Json, KeyringPair } from '@polkadot/keyring/types'; -import { useCreateSession } from '../hooks'; -import { IVoucherDetails } from '@gear-js/api'; +import { UseCreateSessionReturn } from '@/hooks'; type Session = { key: HexString; @@ -25,8 +26,8 @@ type SignlessContext = { session: Session | null | undefined; isSessionReady: boolean; voucherBalance: number; - createSession: (...args: Parameters['createSession']>) => void; - deleteSession: (...args: Parameters['deleteSession']>) => void; + createSession: (...args: Parameters) => void; + deleteSession: (...args: Parameters) => void; voucher: (IVoucherDetails & { id: HexString }) | undefined; isLoading: boolean; setIsLoading: React.Dispatch>; @@ -36,4 +37,31 @@ type SignlessContext = { storageVoucherBalance: number; }; -export type { State, Session, Storage, SignlessContext }; +type ActorId = string; + +type BaseProgramQueryProps = [originAddress?: ActorId, value?: number | string | bigint, atBlock?: `0x${string}`]; + +// TODO: infer type from generic +type ActionsForSession = any; + +type ProgramSession = { + key: ActorId; + expires: number | string | bigint; + allowed_actions: string[]; +}; + +type BaseProgram = + | { + session: { + sessionForTheAccount: (account: ActorId, ...arg2: BaseProgramQueryProps) => Promise; + createSession: ( + key: ActorId, + duration: number | string | bigint, + allowed_actions: Array, + ) => TransactionBuilder; + deleteSession: () => TransactionBuilder; + }; + } + | undefined; + +export type { State, Session, Storage, SignlessContext, BaseProgram, ProgramSession }; diff --git a/frontend/packages/signless-transactions/src/hooks/index.ts b/frontend/packages/signless-transactions/src/hooks/index.ts index 722bf8274..ad5942631 100644 --- a/frontend/packages/signless-transactions/src/hooks/index.ts +++ b/frontend/packages/signless-transactions/src/hooks/index.ts @@ -1,4 +1,6 @@ -import { useCreateSession, Session } from './use-create-session'; +import { useCreateMetadataSession } from './use-create-metadata-session'; +import { useCreateSailsSession } from './use-create-sails-session'; +import { Session, UseCreateSessionReturn } from './use-create-base-session'; import { useSignlessSendMessage, useSignlessSendMessageHandler, @@ -7,5 +9,12 @@ import { import { useIsAvailable } from './use-is-available'; import { useRandomPairOr } from './use-random-pair-or'; -export { useCreateSession, useSignlessSendMessage, useSignlessSendMessageHandler, useIsAvailable, useRandomPairOr }; -export type { SendSignlessMessageOptions, Session }; +export { + useCreateMetadataSession, + useCreateSailsSession, + useSignlessSendMessage, + useSignlessSendMessageHandler, + useIsAvailable, + useRandomPairOr, +}; +export type { SendSignlessMessageOptions, Session, UseCreateSessionReturn }; diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts similarity index 52% rename from frontend/packages/signless-transactions/src/hooks/use-create-session.ts rename to frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts index 725e7e949..385c19f08 100644 --- a/frontend/packages/signless-transactions/src/hooks/use-create-session.ts +++ b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts @@ -1,9 +1,10 @@ -import { HexString, IVoucherDetails, ProgramMetadata, decodeAddress } from '@gear-js/api'; -import { Account, useAccount, useAlert, useApi, useBalanceFormat } from '@gear-js/react-hooks'; -import { AnyJson } from '@polkadot/types/types'; -import { useBatchSignAndSend } from './use-batch-sign-and-send'; -import { web3FromSource } from '@polkadot/extension-dapp'; +import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { HexString, IVoucherDetails } from '@gear-js/api'; +import { useAccount, useAlert, useApi, useBalanceFormat } from '@gear-js/react-hooks'; +import { ISubmittableResult } from '@polkadot/types/types'; import { KeyringPair } from '@polkadot/keyring/types'; + +import { useBatchSignAndSend } from './use-batch-sign-and-send'; import { sendTransaction } from '../utils'; import { useIsAvailable } from '.'; @@ -13,11 +14,27 @@ type Session = { allowedActions: string[]; }; -function useCreateSession( - programId: HexString, - metadata: ProgramMetadata | undefined, - createSignatureType?: (metadata: ProgramMetadata, payloadToSig: Session) => `0x${string}`, -) { +type Options = { + onSuccess: () => void; + onFinally: () => void; +}; + +type CreeateSessionOptions = { + pair?: KeyringPair; + voucherId?: `0x${string}`; + shouldIssueVoucher: boolean; +}; + +type UseCreateSessionReturn = { + createSession: ( + session: Session, + voucherValue: number, + { shouldIssueVoucher, voucherId, pair, ...options }: Options & CreeateSessionOptions, + ) => Promise; + deleteSession: (key: HexString, pair: KeyringPair, options: Options) => Promise; +}; + +function useCreateBaseSession(programId: HexString) { const { api, isApiReady } = useApi(); const alert = useAlert(); const { account } = useAccount(); @@ -26,202 +43,87 @@ function useCreateSession( getFormattedBalanceValue(api?.existentialDeposit.toNumber() || 0).toNumber() + 5; const isDeleteSessionAvailable = useIsAvailable(minRequiredBalanceToDeleteSession, false); const { batchSignAndSend, batchSign, batchSend } = useBatchSignAndSend('all'); + const onError = (message: string) => alert.error(message); const isVoucherExpired = async ({ expiry }: IVoucherDetails) => { if (!isApiReady) throw new Error('API is not initialized'); - const { block } = await api.rpc.chain.getBlock(); const currentBlockNumber = block.header.number.toNumber(); - return currentBlockNumber > expiry; }; // TODO: reuse voucher from context const getLatestVoucher = async (address: string) => { if (!isApiReady) throw new Error('API is not initialized'); - const vouchers = await api.voucher.getAllForAccount(address, programId); - const [entry] = Object.entries(vouchers).sort( ([, voucher], [, nextVoucher]) => nextVoucher.expiry - voucher.expiry, ); - if (!entry) return; const [id, voucher] = entry; - return { ...voucher, id }; }; - - const getMessageExtrinsic = (payload: AnyJson) => { - if (!isApiReady) throw new Error('API is not initialized'); - if (!metadata) throw new Error('Metadata not found'); - - const destination = programId; - const gasLimit = 250000000000; // TODO: replace with calculation after release fix - - return api.message.send({ destination, payload, gasLimit }, metadata); - }; - const getDurationBlocks = (durationMS: number) => { if (!api) { return; } - const blockTimeMs = api.consts.babe.expectedBlockTime.toNumber(); - return (durationMS / blockTimeMs) * 1.05; // +5% to cover transaction time }; - const getVoucherExtrinsic = async (session: Session, voucherValue: number) => { if (!isApiReady) throw new Error('API is not initialized'); - if (!metadata) throw new Error('Metadata not found'); if (!account) throw new Error('Account not found'); - const voucher = await getLatestVoucher(session.key); const durationBlocks = getDurationBlocks(session.duration); - if (!voucher || account.decodedAddress !== voucher.owner) { const { extrinsic } = await api.voucher.issue(session.key, voucherValue, durationBlocks, [programId]); return extrinsic; } - const prolongDuration = durationBlocks; const balanceTopUp = voucherValue; - return api.voucher.update(session.key, voucher.id, { prolongDuration, balanceTopUp }); }; - type Options = { - onSuccess: () => void; - onFinally: () => void; - }; - - type CreeateSessionOptions = { - pair?: KeyringPair; - voucherId?: `0x${string}`; - shouldIssueVoucher: boolean; - }; - - const getAccountSignature = async (metadata: ProgramMetadata, account: Account, payloadToSign: Session) => { - const { signer } = await web3FromSource(account.meta.source); - const { signRaw } = signer; - - console.log('SIGNATURE:'); - console.log('METADATA'); - console.log(metadata); - console.log('ACCOUNT'); - console.log(account); - console.log('PAYLOAD_TO_SIGN'); - console.log(payloadToSign); - console.log('web3FromSource'); - console.log(web3FromSource); - console.log('SIGNER'); - console.log(signer); - console.log('signRaw'); - console.log(signRaw); - - if (!signRaw) { - throw new Error('signRaw is not a function'); - } - - if (metadata.types?.others?.output === null) { - throw new Error(`Metadata type doesn't exist`); - } - - console.log('metadata.types?.others?.output'); - console.log(metadata.types?.others?.output); - - const hexToSign = createSignatureType - ? createSignatureType(metadata, payloadToSign) - : metadata.createType(metadata.types.others.output, payloadToSign).toHex(); - - console.log('HEXtoSIGN: ', hexToSign); - return signRaw({ address: account.address, data: hexToSign, type: 'bytes' }); - }; - const createSession = async ( - session: Session, - voucherValue: number, - { shouldIssueVoucher, voucherId, pair, ...options }: Options & CreeateSessionOptions, + const signAndSendDeleteSession = async ( + messageExtrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>, + key: HexString, + pair: KeyringPair, + options: Options, ) => { if (!isApiReady) throw new Error('API is not initialized'); if (!account) throw new Error('Account not found'); - if (!metadata) throw new Error('Metadata not found'); - - if (voucherId && pair) { - const { signature } = await getAccountSignature(metadata, account, { - ...session, - key: decodeAddress(pair.address), - }); - - const messageExtrinsic = getMessageExtrinsic({ - CreateSession: { ...session, signature }, - }); - - const voucherExtrinsic = api.voucher.call(voucherId, { SendMessage: messageExtrinsic }); - - await sendTransaction(voucherExtrinsic, pair, ['UserMessageSent'], { ...options, onError }); - - return; - } - - const messageExtrinsic = getMessageExtrinsic({ CreateSession: session }); - - const txs = shouldIssueVoucher - ? [messageExtrinsic, await getVoucherExtrinsic(session, voucherValue)] - : [messageExtrinsic]; - - batchSignAndSend(txs, { ...options, onError }); - }; - - const deleteSession = async (key: HexString, pair: KeyringPair, options: Options) => { - if (!isApiReady) throw new Error('API is not initialized'); - if (!account) throw new Error('Account not found'); - if (!metadata) throw new Error('Metadata not found'); - if (!isDeleteSessionAvailable) { alert.error('Low balance on account to disable session'); options.onFinally(); throw new Error('Low balance on account to disable session'); } - - const messageExtrinsic = getMessageExtrinsic({ - DeleteSessionFromAccount: null, - }); - const txs = [messageExtrinsic]; - const voucher = await getLatestVoucher(key); if (!voucher) return batchSignAndSend(txs, { ...options, onError }); - const isOwner = account.decodedAddress === voucher.owner; const isExpired = await isVoucherExpired(voucher); - if (isOwner) { // revoke on onSuccess is not called to avoid second signatures popup const revokeExtrinsic = api.voucher.revoke(key, voucher.id); txs.push(revokeExtrinsic); } - // We need to sign transactions before sending declineExtrinsic; // Otherwise, if the signing is canceled, the voucher will be invalid. const signedTxs = await batchSign(txs, options); - if (!signedTxs) { throw new Error('Transaction sign canceled'); } - if (!isExpired) { const declineExtrinsic = api.voucher.call(voucher.id, { DeclineVoucher: null }); await sendTransaction(declineExtrinsic, pair, ['VoucherDeclined'], options); } - batchSend(signedTxs, { ...options, onError }); }; - return { createSession, deleteSession }; + return { getVoucherExtrinsic, signAndSendDeleteSession }; } -export { useCreateSession }; +export { useCreateBaseSession }; -export type { Session }; +export type { Session, Options, CreeateSessionOptions, UseCreateSessionReturn }; diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts new file mode 100644 index 000000000..0298da3da --- /dev/null +++ b/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts @@ -0,0 +1,114 @@ +import { HexString, ProgramMetadata, decodeAddress } from '@gear-js/api'; +import { Account, useAccount, useAlert, useApi } from '@gear-js/react-hooks'; +import { AnyJson } from '@polkadot/types/types'; +import { useBatchSignAndSend } from './use-batch-sign-and-send'; +import { web3FromSource } from '@polkadot/extension-dapp'; +import { KeyringPair } from '@polkadot/keyring/types'; +import { sendTransaction } from '../utils'; +import { CreeateSessionOptions, Options, Session, useCreateBaseSession } from './use-create-base-session'; + +function useCreateMetadataSession( + programId: HexString, + metadata: ProgramMetadata | undefined, + createSignatureType?: (metadata: ProgramMetadata, payloadToSig: Session) => `0x${string}`, +) { + const { api, isApiReady } = useApi(); + const alert = useAlert(); + const { account } = useAccount(); + const { batchSignAndSend } = useBatchSignAndSend('all'); + + const onError = (message: string) => alert.error(message); + + const { getVoucherExtrinsic, signAndSendDeleteSession } = useCreateBaseSession(programId); + + const getMessageExtrinsic = (payload: AnyJson) => { + if (!isApiReady) throw new Error('API is not initialized'); + if (!metadata) throw new Error('Metadata not found'); + + const destination = programId; + const gasLimit = 250000000000; // TODO: replace with calculation after release fix + + return api.message.send({ destination, payload, gasLimit }, metadata); + }; + + const getAccountSignature = async (metadata: ProgramMetadata, account: Account, payloadToSign: Session) => { + const { signer } = await web3FromSource(account.meta.source); + const { signRaw } = signer; + + console.log('SIGNATURE:'); + console.log('METADATA', metadata); + console.log('ACCOUNT', account); + console.log('PAYLOAD_TO_SIGN', payloadToSign); + console.log('web3FromSource', web3FromSource); + console.log('SIGNER', signer); + console.log('signRaw', signRaw); + + if (!signRaw) { + throw new Error('signRaw is not a function'); + } + + if (metadata.types?.others?.output === null) { + throw new Error(`Metadata type doesn't exist`); + } + + console.log('metadata.types?.others?.output'); + console.log(metadata.types?.others?.output); + + const hexToSign = createSignatureType + ? createSignatureType(metadata, payloadToSign) + : metadata.createType(metadata.types.others.output, payloadToSign).toHex(); + + console.log('HEXtoSIGN: ', hexToSign); + return signRaw({ address: account.address, data: hexToSign, type: 'bytes' }); + }; + + const createSession = async ( + session: Session, + voucherValue: number, + { shouldIssueVoucher, voucherId, pair, ...options }: Options & CreeateSessionOptions, + ) => { + if (!isApiReady) throw new Error('API is not initialized'); + if (!account) throw new Error('Account not found'); + if (!metadata) throw new Error('Metadata not found'); + + if (voucherId && pair) { + const { signature } = await getAccountSignature(metadata, account, { + ...session, + key: decodeAddress(pair.address), + }); + + const messageExtrinsic = getMessageExtrinsic({ + CreateSession: { ...session, signature }, + }); + + const voucherExtrinsic = api.voucher.call(voucherId, { SendMessage: messageExtrinsic }); + + await sendTransaction(voucherExtrinsic, pair, ['UserMessageSent'], { ...options, onError }); + + return; + } + + const messageExtrinsic = getMessageExtrinsic({ CreateSession: session }); + + const txs = shouldIssueVoucher + ? [messageExtrinsic, await getVoucherExtrinsic(session, voucherValue)] + : [messageExtrinsic]; + + batchSignAndSend(txs, { ...options, onError }); + }; + + const deleteSession = async (key: HexString, pair: KeyringPair, options: Options) => { + if (!account) throw new Error('Account not found'); + if (!metadata) throw new Error('Metadata not found'); + + const messageExtrinsic = getMessageExtrinsic({ + DeleteSessionFromAccount: null, + }); + + signAndSendDeleteSession(messageExtrinsic, key, pair, options); + }; + + return { createSession, deleteSession }; +} + +export { useCreateMetadataSession }; diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts new file mode 100644 index 000000000..83f9db4d0 --- /dev/null +++ b/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts @@ -0,0 +1,66 @@ +import { HexString } from '@gear-js/api'; +import { useAccount, useAlert, useApi, usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { useBatchSignAndSend } from './use-batch-sign-and-send'; +import { KeyringPair } from '@polkadot/keyring/types'; +import { BaseProgram } from '@/context/types'; +import { CreeateSessionOptions, Options, Session, useCreateBaseSession } from './use-create-base-session'; + +function useCreateSailsSession(programId: HexString, program: BaseProgram) { + const { isApiReady } = useApi(); + const alert = useAlert(); + const { account } = useAccount(); + const { batchSignAndSend } = useBatchSignAndSend('all'); + const onError = (message: string) => alert.error(message); + const { getVoucherExtrinsic, signAndSendDeleteSession } = useCreateBaseSession(programId); + + const { prepareTransactionAsync: prepareCreateSession } = usePrepareProgramTransaction({ + program, + serviceName: 'session', + functionName: 'createSession', + }); + + const { prepareTransactionAsync: prepareDeleteSession } = usePrepareProgramTransaction({ + program, + serviceName: 'session', + functionName: 'deleteSession', + }); + + const createSession = async ( + session: Session, + voucherValue: number, + { shouldIssueVoucher, voucherId, pair, ...options }: Options & CreeateSessionOptions, + ) => { + if (!isApiReady) throw new Error('API is not initialized'); + if (!account) throw new Error('Account not found'); + + const { key, duration, allowedActions } = session; + + const { transaction } = await prepareCreateSession({ + account: { addressOrPair: pair ? pair.address : account.decodedAddress }, + args: [key, duration, allowedActions], + voucherId, + }); + const messageExtrinsic = transaction.extrinsic; + + const txs = shouldIssueVoucher + ? [messageExtrinsic, await getVoucherExtrinsic(session, voucherValue)] + : [messageExtrinsic]; + + batchSignAndSend(txs, { ...options, onError }); + }; + + const deleteSession = async (key: HexString, pair: KeyringPair, options: Options) => { + if (!account) throw new Error('Account not found'); + + const { transaction } = await prepareDeleteSession({ + account: { addressOrPair: account.decodedAddress }, + args: [], + }); + + signAndSendDeleteSession(transaction.extrinsic, key, pair, options); + }; + + return { createSession, deleteSession }; +} + +export { useCreateSailsSession }; diff --git a/frontend/packages/signless-transactions/src/index.ts b/frontend/packages/signless-transactions/src/index.ts index ac710a3b2..10d52b15e 100644 --- a/frontend/packages/signless-transactions/src/index.ts +++ b/frontend/packages/signless-transactions/src/index.ts @@ -4,7 +4,8 @@ import { useSignlessTransactions, DEFAULT_SIGNLESS_CONTEXT, SignlessContext, - SignlessTransactionsProviderProps, + SignlessTransactionsMetadataProviderProps, + SignlessTransactionsSailsProviderProps, } from './context'; import { useSignlessSendMessage, useSignlessSendMessageHandler, SendSignlessMessageOptions } from './hooks'; @@ -19,4 +20,9 @@ export { DEFAULT_SIGNLESS_CONTEXT, }; -export type { SendSignlessMessageOptions, SignlessContext, SignlessTransactionsProviderProps }; +export type { + SendSignlessMessageOptions, + SignlessContext, + SignlessTransactionsMetadataProviderProps, + SignlessTransactionsSailsProviderProps, +}; From 379db350011fa6096ce767a5a8b965c0aaed2e02 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 8 Aug 2024 13:11:01 +0400 Subject: [PATCH 02/17] remove unused code --- .../src/app/utils/use-make-transaction.ts | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts b/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts index c46f30a5d..5570fb0e3 100644 --- a/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts +++ b/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts @@ -1,7 +1,6 @@ import { useEzTransactions } from '@dapps-frontend/ez-transactions'; import { useAccount } from '@gear-js/react-hooks'; import { web3FromSource } from '@polkadot/extension-dapp'; -import { TransactionBuilder } from 'sails-js'; import { useProgram } from './sails'; const usePrepareEzTransactionParams = () => { @@ -36,37 +35,4 @@ const usePrepareEzTransactionParams = () => { return { prepareEzTransactionParams }; }; -const useMakeTransaction = () => { - const gasLimit = 250_000_000_000n; - const { account } = useAccount(); - - const { gasless, signless } = useEzTransactions(); - const { pair, voucher } = signless; - - return async (transactrionBuilder: TransactionBuilder) => { - if (!account?.decodedAddress) { - throw new Error('No account found!'); - } - - let voucherId = voucher?.id || gasless.voucherId; - if (account && gasless.isEnabled && !gasless.voucherId && !signless.isActive) { - voucherId = await gasless.requestVoucher(account.address); - } - - const injector = await web3FromSource(account.meta.source); - - if (pair) { - transactrionBuilder.withAccount(pair); - } else { - transactrionBuilder.withAccount(account.decodedAddress, { signer: injector.signer }); - } - - if (voucherId) { - transactrionBuilder.withVoucher(voucherId); - } - - return await transactrionBuilder.withGas(gasLimit); - }; -}; - -export { usePrepareEzTransactionParams, useMakeTransaction }; +export { usePrepareEzTransactionParams }; From 287718be14b797f5d4646b497611f4bfca5a5ed9 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 8 Aug 2024 16:37:10 +0400 Subject: [PATCH 03/17] send createGameMessage from base account --- ...saction.ts => use-prepare-ez-transaction-params.ts} | 10 ++++++---- .../components/registration/registration.tsx | 4 ---- .../multiplayer/hooks/use-process-with-multiplayer.tsx | 2 +- .../sails/messages/use-cancel-game-message.ts | 2 +- .../sails/messages/use-create-game-message.ts | 4 ++-- .../sails/messages/use-delete-player-message.ts | 2 +- .../sails/messages/use-join-game-message.ts | 2 +- .../sails/messages/use-leave-game-message.ts | 2 +- .../sails/messages/use-make-move-message.ts | 2 +- .../sails/messages/use-verify-placement-message.ts | 2 +- .../sails/messages/use-make-move-message.ts | 2 +- .../sails/messages/use-start-game-message.ts | 2 +- 12 files changed, 17 insertions(+), 19 deletions(-) rename frontend/apps/battleship-zk/src/app/utils/{use-make-transaction.ts => use-prepare-ez-transaction-params.ts} (77%) diff --git a/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts b/frontend/apps/battleship-zk/src/app/utils/use-prepare-ez-transaction-params.ts similarity index 77% rename from frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts rename to frontend/apps/battleship-zk/src/app/utils/use-prepare-ez-transaction-params.ts index 5570fb0e3..a8b5a546f 100644 --- a/frontend/apps/battleship-zk/src/app/utils/use-make-transaction.ts +++ b/frontend/apps/battleship-zk/src/app/utils/use-prepare-ez-transaction-params.ts @@ -10,12 +10,14 @@ const usePrepareEzTransactionParams = () => { const { signless, gasless } = useEzTransactions(); const { pair, voucher } = signless; - const prepareEzTransactionParams = async () => { + const prepareEzTransactionParams = async (sendFromBaseAccount?: boolean) => { if (!program) throw new Error('program does not found'); if (!account) throw new Error('Account not found'); - const sessionForAccount = pair ? account.decodedAddress : null; - let voucherId = voucher?.id || gasless.voucherId; + const sendFromPair = pair && voucher?.id && !sendFromBaseAccount; + const sessionForAccount = sendFromPair ? account.decodedAddress : null; + + let voucherId = sendFromPair ? voucher?.id : gasless.voucherId; if (account && gasless.isEnabled && !gasless.voucherId && !signless.isActive) { voucherId = await gasless.requestVoucher(account.address); } @@ -24,7 +26,7 @@ const usePrepareEzTransactionParams = () => { return { sessionForAccount, - account: pair + account: sendFromPair ? { addressOrPair: pair } : { addressOrPair: account.decodedAddress, signerOptions: { signer: injector.signer } }, voucherId, diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx index d799913a0..8cc006283 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import clsx from 'clsx'; -import { useAtom } from 'jotai'; import { Button } from '@gear-js/vara-ui'; import { Heading } from '@/components/ui/heading'; import { Text } from '@/components/ui/text'; @@ -19,7 +18,6 @@ import { useMultiplayerGame } from '../../hooks'; import styles from './Registration.module.scss'; import { useDeleteGameMessage } from '../../sails/messages/use-delete-player-message'; import { ROUTES } from '@/app/consts'; -import { gameEndResultAtom } from '../../atoms'; type UserProps = { name: string; @@ -61,7 +59,6 @@ export function Registration() { const { account } = useAccount(); const { game, triggerGame } = useMultiplayerGame(); const { pending, setPending } = usePending(); - const [gameEndResult] = useAtom(gameEndResultAtom); useEventPlayerJoinedGame(); useEventGameCancelled(); @@ -82,7 +79,6 @@ export function Registration() { const { response } = await transaction.signAndSend(); await response(); - await triggerGame(); } catch (err) { console.log(err); } finally { diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx index 44aaae71a..d4ee52fc6 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx @@ -1,5 +1,5 @@ import { useAccount } from '@gear-js/react-hooks'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useMoveTransaction, usePending } from '@/features/game/hooks'; import { useMultiplayerGame } from './use-multiplayer-game'; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts index 38cfeb4d7..080d0a3e9 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useCancelGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts index a16008673..36d07480e 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useCreateGameMessage = () => { const program = useProgram(); @@ -12,7 +12,7 @@ export const useCreateGameMessage = () => { const { prepareEzTransactionParams } = usePrepareEzTransactionParams(); const createGameMessage = async (name: string) => { - const { sessionForAccount, ...params } = await prepareEzTransactionParams(); + const { sessionForAccount, ...params } = await prepareEzTransactionParams(true); const { transaction } = await prepareTransactionAsync({ args: [name, sessionForAccount], ...params, diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts index b6022b526..b5a1de5f3 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useDeleteGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts index 8c9e58c58..8a7453c54 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useJoinGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts index f1a7767b2..acd1026b8 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useLeaveGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts index 969163dfb..3959eec4e 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts @@ -1,7 +1,7 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; import { VerificationVariables } from '@/app/utils/sails/lib/lib'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useMakeMoveMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts index 38f94e549..d34cdcae6 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; import { ProofBytes, PublicStartInput } from '@/app/utils/sails/lib/lib'; export const useVerifyPlacementMessage = () => { diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts index 9a0d363f2..7da884950 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts +++ b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts @@ -1,7 +1,7 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; import { VerificationVariables } from '@/app/utils/sails/lib/lib'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useMakeMoveMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts index f5f69f9c3..ad002c11c 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-make-transaction'; +import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; import { ProofBytes, PublicStartInput } from '@/app/utils/sails/lib/lib'; export const useStartGameMessage = () => { From 492153aabc168a4271cfeaaa9861cdc767f34858 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 8 Aug 2024 18:17:38 +0400 Subject: [PATCH 04/17] fix registration cancel --- .../multiplayer/components/registration/registration.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx index 8cc006283..4e62a1707 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx @@ -57,7 +57,7 @@ export function Registration() { const { cancelGameMessage } = useCancelGameMessage(); const { deletePlayerMessage } = useDeleteGameMessage(); const { account } = useAccount(); - const { game, triggerGame } = useMultiplayerGame(); + const { game, triggerGame, resetGameState } = useMultiplayerGame(); const { pending, setPending } = usePending(); useEventPlayerJoinedGame(); @@ -79,6 +79,7 @@ export function Registration() { const { response } = await transaction.signAndSend(); await response(); + resetGameState() } catch (err) { console.log(err); } finally { From ced213e94adb897889d4031142f1a60173e6a09a Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Fri, 9 Aug 2024 10:19:14 +0400 Subject: [PATCH 05/17] fix: display signless modal above the dialog --- .../src/components/ui/modal/Modal.tsx | 9 +++++++-- .../game-found-modal/GameFoundModal.module.scss | 14 ++++++++++++-- .../game-found-modal/game-found-modal.tsx | 7 ++++++- .../apps/battleship-zk/src/pages/login.module.scss | 3 +++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx b/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx index da39c0c11..c4878cd4a 100644 --- a/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx +++ b/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx @@ -15,16 +15,21 @@ type Props = React.PropsWithChildren & { }; onClose: () => void; closeOnMissclick?: boolean; + showModalMode?: boolean; }; -export function Modal({ heading, children, className, onClose, closeOnMissclick = true }: Props) { +export function Modal({ heading, children, className, onClose, closeOnMissclick = true, showModalMode = true }: Props) { const ref = useRef(null); const disableScroll = () => document.body.classList.add('modal-open'); const enableScroll = () => document.body.classList.remove('modal-open'); const open = () => { - ref.current?.showModal(); + if (showModalMode) { + ref.current?.showModal(); + } else { + ref.current?.show(); + } disableScroll(); }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/GameFoundModal.module.scss b/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/GameFoundModal.module.scss index 09686ad38..0759a5025 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/GameFoundModal.module.scss +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/GameFoundModal.module.scss @@ -1,5 +1,14 @@ .modal { - min-width: 670px; + position: relative; + &::before { + position: fixed; + content: ''; + min-width: 467px; + height: 100vh; + top:0; + background: #000000A6; + backdrop-filter: blur(10px); + } } .container { @@ -26,8 +35,9 @@ .modalWrapper { background: #fff; + border-radius: 8px 8px 0px 0px; } .mainText { opacity: 0.7; -} \ No newline at end of file +} diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx index 3a91424d1..4d703ede6 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx @@ -69,7 +69,12 @@ function GameFoundModal({ entryFee, players, gasAmount, onSubmit, onClose }: Pro }; return ( - +

To proceed, review the parameters of the gaming session and click the “Join” button. If applicable, you will diff --git a/frontend/apps/battleship-zk/src/pages/login.module.scss b/frontend/apps/battleship-zk/src/pages/login.module.scss index e26ff18e6..262248f77 100644 --- a/frontend/apps/battleship-zk/src/pages/login.module.scss +++ b/frontend/apps/battleship-zk/src/pages/login.module.scss @@ -32,6 +32,9 @@ flex-direction: column; padding: 0 32px; align-items: center; + > div:first-child { + margin-bottom: 32px; + } } .startGameButton { From 293cc476374adbe726901a0323e4d15d5b65bb38 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Fri, 9 Aug 2024 13:11:10 +0400 Subject: [PATCH 06/17] Fix review --- .../sails/messages/use-cancel-game-message.ts | 2 +- .../sails/messages/use-create-game-message.ts | 2 +- .../messages/use-delete-player-message.ts | 2 +- .../sails/messages/use-join-game-message.ts | 2 +- .../sails/messages/use-leave-game-message.ts | 2 +- .../sails/messages/use-make-move-message.ts | 2 +- .../messages/use-verify-placement-message.ts | 2 +- .../sails/messages/use-make-move-message.ts | 2 +- .../sails/messages/use-start-game-message.ts | 2 +- .../ez-transactions/src/hooks/index.ts | 3 +++ .../use-prepare-ez-transaction-params.ts | 12 ++------- .../packages/ez-transactions/src/index.ts | 2 ++ .../src/hooks/use-create-base-session.ts | 16 ++++++++++- .../src/hooks/use-create-metadata-session.ts | 16 +++-------- .../src/hooks/use-create-sails-session.ts | 27 ++++++------------- 15 files changed, 42 insertions(+), 52 deletions(-) create mode 100644 frontend/packages/ez-transactions/src/hooks/index.ts rename frontend/{apps/battleship-zk/src/app/utils => packages/ez-transactions/src/hooks}/use-prepare-ez-transaction-params.ts (66%) diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts index 080d0a3e9..40f69197b 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-cancel-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useCancelGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts index 36d07480e..bcdb4dc7e 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-create-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useCreateGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts index b5a1de5f3..53f31f63a 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-delete-player-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useDeleteGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts index 8a7453c54..3ba4d0e8f 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-join-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useJoinGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts index acd1026b8..058636fd8 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-leave-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useLeaveGameMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts index 3959eec4e..9203cde76 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-make-move-message.ts @@ -1,7 +1,7 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; import { VerificationVariables } from '@/app/utils/sails/lib/lib'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useMakeMoveMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts index d34cdcae6..c281c508e 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/messages/use-verify-placement-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; import { ProofBytes, PublicStartInput } from '@/app/utils/sails/lib/lib'; export const useVerifyPlacementMessage = () => { diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts index 7da884950..97e3b2c61 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts +++ b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-make-move-message.ts @@ -1,7 +1,7 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; import { VerificationVariables } from '@/app/utils/sails/lib/lib'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; export const useMakeMoveMessage = () => { const program = useProgram(); diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts index ad002c11c..3e21b28b8 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts +++ b/frontend/apps/battleship-zk/src/features/singleplayer/sails/messages/use-start-game-message.ts @@ -1,6 +1,6 @@ import { usePrepareProgramTransaction } from '@gear-js/react-hooks'; +import { usePrepareEzTransactionParams } from '@dapps-frontend/ez-transactions'; import { useProgram } from '@/app/utils/sails'; -import { usePrepareEzTransactionParams } from '@/app/utils/use-prepare-ez-transaction-params'; import { ProofBytes, PublicStartInput } from '@/app/utils/sails/lib/lib'; export const useStartGameMessage = () => { diff --git a/frontend/packages/ez-transactions/src/hooks/index.ts b/frontend/packages/ez-transactions/src/hooks/index.ts new file mode 100644 index 000000000..1e5c16eb0 --- /dev/null +++ b/frontend/packages/ez-transactions/src/hooks/index.ts @@ -0,0 +1,3 @@ +import { usePrepareEzTransactionParams } from './use-prepare-ez-transaction-params'; + +export { usePrepareEzTransactionParams }; \ No newline at end of file diff --git a/frontend/apps/battleship-zk/src/app/utils/use-prepare-ez-transaction-params.ts b/frontend/packages/ez-transactions/src/hooks/use-prepare-ez-transaction-params.ts similarity index 66% rename from frontend/apps/battleship-zk/src/app/utils/use-prepare-ez-transaction-params.ts rename to frontend/packages/ez-transactions/src/hooks/use-prepare-ez-transaction-params.ts index a8b5a546f..d2321cd12 100644 --- a/frontend/apps/battleship-zk/src/app/utils/use-prepare-ez-transaction-params.ts +++ b/frontend/packages/ez-transactions/src/hooks/use-prepare-ez-transaction-params.ts @@ -1,17 +1,13 @@ -import { useEzTransactions } from '@dapps-frontend/ez-transactions'; import { useAccount } from '@gear-js/react-hooks'; -import { web3FromSource } from '@polkadot/extension-dapp'; -import { useProgram } from './sails'; +import { useEzTransactions } from '../context'; const usePrepareEzTransactionParams = () => { const gasLimit = 250_000_000_000n; - const program = useProgram(); const { account } = useAccount(); const { signless, gasless } = useEzTransactions(); const { pair, voucher } = signless; const prepareEzTransactionParams = async (sendFromBaseAccount?: boolean) => { - if (!program) throw new Error('program does not found'); if (!account) throw new Error('Account not found'); const sendFromPair = pair && voucher?.id && !sendFromBaseAccount; @@ -22,13 +18,9 @@ const usePrepareEzTransactionParams = () => { voucherId = await gasless.requestVoucher(account.address); } - const injector = await web3FromSource(account.meta.source); - return { sessionForAccount, - account: sendFromPair - ? { addressOrPair: pair } - : { addressOrPair: account.decodedAddress, signerOptions: { signer: injector.signer } }, + account: sendFromPair ? { addressOrPair: pair } : undefined, voucherId, gasLimit, }; diff --git a/frontend/packages/ez-transactions/src/index.ts b/frontend/packages/ez-transactions/src/index.ts index 422f9bc64..82f6b4c80 100644 --- a/frontend/packages/ez-transactions/src/index.ts +++ b/frontend/packages/ez-transactions/src/index.ts @@ -1,5 +1,6 @@ import { EzTransactionsProvider, useEzTransactions } from './context'; import { EzTransactionsSwitch, EzSignlessTransactions, EzGaslessTransactions } from './components'; +import { usePrepareEzTransactionParams } from './hooks'; export * from '@dapps-frontend/gasless-transactions'; export * from '@dapps-frontend/signless-transactions'; @@ -9,4 +10,5 @@ export { EzTransactionsSwitch, EzSignlessTransactions, EzGaslessTransactions, + usePrepareEzTransactionParams, }; diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts index 385c19f08..e859cf325 100644 --- a/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts +++ b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts @@ -85,6 +85,20 @@ function useCreateBaseSession(programId: HexString) { return api.voucher.update(session.key, voucher.id, { prolongDuration, balanceTopUp }); }; + const signAndSendCreateSession = async ( + messageExtrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>, + session: Session, + voucherValue: number, + options: Options, + shouldIssueVoucher?: boolean, + ) => { + const txs = shouldIssueVoucher + ? [messageExtrinsic, await getVoucherExtrinsic(session, voucherValue)] + : [messageExtrinsic]; + + batchSignAndSend(txs, { ...options, onError }); + }; + const signAndSendDeleteSession = async ( messageExtrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>, key: HexString, @@ -121,7 +135,7 @@ function useCreateBaseSession(programId: HexString) { batchSend(signedTxs, { ...options, onError }); }; - return { getVoucherExtrinsic, signAndSendDeleteSession }; + return { signAndSendDeleteSession, signAndSendCreateSession, onError }; } export { useCreateBaseSession }; diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts index 0298da3da..719bb4f7f 100644 --- a/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts +++ b/frontend/packages/signless-transactions/src/hooks/use-create-metadata-session.ts @@ -1,7 +1,6 @@ import { HexString, ProgramMetadata, decodeAddress } from '@gear-js/api'; -import { Account, useAccount, useAlert, useApi } from '@gear-js/react-hooks'; +import { Account, useAccount, useApi } from '@gear-js/react-hooks'; import { AnyJson } from '@polkadot/types/types'; -import { useBatchSignAndSend } from './use-batch-sign-and-send'; import { web3FromSource } from '@polkadot/extension-dapp'; import { KeyringPair } from '@polkadot/keyring/types'; import { sendTransaction } from '../utils'; @@ -13,13 +12,9 @@ function useCreateMetadataSession( createSignatureType?: (metadata: ProgramMetadata, payloadToSig: Session) => `0x${string}`, ) { const { api, isApiReady } = useApi(); - const alert = useAlert(); const { account } = useAccount(); - const { batchSignAndSend } = useBatchSignAndSend('all'); - const onError = (message: string) => alert.error(message); - - const { getVoucherExtrinsic, signAndSendDeleteSession } = useCreateBaseSession(programId); + const { signAndSendCreateSession, signAndSendDeleteSession, onError } = useCreateBaseSession(programId); const getMessageExtrinsic = (payload: AnyJson) => { if (!isApiReady) throw new Error('API is not initialized'); @@ -89,12 +84,7 @@ function useCreateMetadataSession( } const messageExtrinsic = getMessageExtrinsic({ CreateSession: session }); - - const txs = shouldIssueVoucher - ? [messageExtrinsic, await getVoucherExtrinsic(session, voucherValue)] - : [messageExtrinsic]; - - batchSignAndSend(txs, { ...options, onError }); + signAndSendCreateSession(messageExtrinsic, session, voucherValue, options, shouldIssueVoucher); }; const deleteSession = async (key: HexString, pair: KeyringPair, options: Options) => { diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts index 83f9db4d0..e2c359f41 100644 --- a/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts +++ b/frontend/packages/signless-transactions/src/hooks/use-create-sails-session.ts @@ -1,17 +1,13 @@ import { HexString } from '@gear-js/api'; -import { useAccount, useAlert, useApi, usePrepareProgramTransaction } from '@gear-js/react-hooks'; -import { useBatchSignAndSend } from './use-batch-sign-and-send'; +import { useAccount, useApi, usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { KeyringPair } from '@polkadot/keyring/types'; import { BaseProgram } from '@/context/types'; import { CreeateSessionOptions, Options, Session, useCreateBaseSession } from './use-create-base-session'; function useCreateSailsSession(programId: HexString, program: BaseProgram) { const { isApiReady } = useApi(); - const alert = useAlert(); const { account } = useAccount(); - const { batchSignAndSend } = useBatchSignAndSend('all'); - const onError = (message: string) => alert.error(message); - const { getVoucherExtrinsic, signAndSendDeleteSession } = useCreateBaseSession(programId); + const { signAndSendCreateSession, signAndSendDeleteSession } = useCreateBaseSession(programId); const { prepareTransactionAsync: prepareCreateSession } = usePrepareProgramTransaction({ program, @@ -30,33 +26,26 @@ function useCreateSailsSession(programId: HexString, program: BaseProgram) { voucherValue: number, { shouldIssueVoucher, voucherId, pair, ...options }: Options & CreeateSessionOptions, ) => { + console.log("🚀 ~ useCreateSailsSession ~ session:", session) + console.log("🚀 ~ useCreateSailsSession ~ voucherId:", voucherId) + console.log("🚀 ~ useCreateSailsSession ~ pair:", pair) if (!isApiReady) throw new Error('API is not initialized'); if (!account) throw new Error('Account not found'); const { key, duration, allowedActions } = session; const { transaction } = await prepareCreateSession({ - account: { addressOrPair: pair ? pair.address : account.decodedAddress }, + account: pair ? { addressOrPair: pair.address } : undefined, args: [key, duration, allowedActions], voucherId, }); const messageExtrinsic = transaction.extrinsic; - const txs = shouldIssueVoucher - ? [messageExtrinsic, await getVoucherExtrinsic(session, voucherValue)] - : [messageExtrinsic]; - - batchSignAndSend(txs, { ...options, onError }); + signAndSendCreateSession(messageExtrinsic, session, voucherValue, options, shouldIssueVoucher); }; const deleteSession = async (key: HexString, pair: KeyringPair, options: Options) => { - if (!account) throw new Error('Account not found'); - - const { transaction } = await prepareDeleteSession({ - account: { addressOrPair: account.decodedAddress }, - args: [], - }); - + const { transaction } = await prepareDeleteSession({ args: [] }); signAndSendDeleteSession(transaction.extrinsic, key, pair, options); }; From c191165b1a589acc727aea528b3b7550c2c76d58 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Fri, 9 Aug 2024 16:52:52 +0400 Subject: [PATCH 07/17] remove required gas amount text --- .../create-game-form/create-game-form.tsx | 13 ------------- .../game-found-modal/game-found-modal.tsx | 13 +------------ .../components/join-game-form/join-game-form.tsx | 3 --- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx index 4b36a8b36..761cffe78 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx @@ -73,18 +73,6 @@ function CreateGameForm({ onCancel }: Props) { } }; - const items = [ - { - name: 'Required gas amount ', - value: ( - <> - {1.21} VARA - - ), - key: '3', - }, - ]; - return (

@@ -119,7 +107,6 @@ function CreateGameForm({ onCancel }: Props) { {createErrors.name}
-
+
+ + ) : null; +} diff --git a/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/index.tsx b/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/index.tsx new file mode 100644 index 000000000..de9753514 --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/index.tsx @@ -0,0 +1,3 @@ +import GameCancelledModal from './game-cancelled-modal'; + +export { GameCancelledModal }; diff --git a/frontend/apps/battleship-zk/src/features/game/components/index.ts b/frontend/apps/battleship-zk/src/features/game/components/index.ts index 57b5f9e2a..e95112222 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/index.ts +++ b/frontend/apps/battleship-zk/src/features/game/components/index.ts @@ -1,5 +1,6 @@ export { ShipArrangement } from './ship-arrangement'; export { GameProcess } from './game-process'; export { GameEndModal } from './game-end-modal'; +export { GameCancelledModal } from './game-cancelled-modal'; export { Map } from './map'; export { Illustration } from './illustration'; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx index 286730b57..408e2e019 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx @@ -39,7 +39,7 @@ function CreateGameForm({ onCancel }: Props) { // ! TODO: check this logic const createForm = useForm({ initialValues: { - fee: existentialDeposit + 5 || 0, + fee: 0, name: '', }, validate: { diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx index e86adc001..d55c8bb5f 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/registration/registration.tsx @@ -6,13 +6,18 @@ import { Heading } from '@/components/ui/heading'; import { Text } from '@/components/ui/text'; import { VaraIcon } from '@/components/layout/vara-svg'; import { usePending } from '@/features/game/hooks'; -import { Illustration } from '@/features/game/components'; +import { GameCancelledModal, Illustration } from '@/features/game/components'; import { getVaraAddress, useAccount, useAlert, useBalanceFormat } from '@gear-js/react-hooks'; import { decodeAddress } from '@gear-js/api'; import { stringShorten } from '@polkadot/util'; import { copyToClipboard } from '@/app/utils/utils'; import { ReactComponent as FilledCrossSVG } from '../../assets/icons/filled-cross.svg'; -import { useEventGameCancelled, useEventPlayerJoinedGame } from '../../sails/events'; +import { + useEventGameCancelled, + useEventPlayerJoinedGame, + useEventPlayerDeleted, + useEventGameLeft, +} from '../../sails/events'; import { useCancelGameMessage } from '../../sails/messages'; import { useMultiplayerGame } from '../../hooks'; import styles from './Registration.module.scss'; @@ -62,6 +67,8 @@ export function Registration() { useEventPlayerJoinedGame(); useEventGameCancelled(); + const { isPlayerDeleted, onPlayerDeletedModalClose } = useEventPlayerDeleted(); + useEventGameLeft(); const startGame = () => { navigate(ROUTES.GAME); @@ -166,6 +173,12 @@ export function Registration() {
)} + + ); } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/consts.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/consts.ts index 989dc2039..741ac6448 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/consts.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/consts.ts @@ -6,6 +6,9 @@ const EVENT_NAME = { SUBSCRIBE_TO_MOVE_MADE_EVENT: 'subscribeToMoveMadeEvent', SUBSCRIBE_TO_END_GAME_EVENT: 'subscribeToEndGameEvent', SUBSCRIBE_TO_GAME_CANCELED_EVENT: 'subscribeToGameCanceledEvent', + SUBSCRIBE_TO_GAME_LEFT_EVENT: 'subscribeToGameLeftEvent', + SUBSCRIBE_TO_PLAYER_DELETED_EVENT: 'subscribeToPlayerDeletedEvent', + } as const; export { SERVICE_NAME, EVENT_NAME }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/index.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/index.ts index 45ce3cd91..2ba3a3746 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/index.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/index.ts @@ -1,5 +1,7 @@ export { useEventGameCancelled } from './use-event-game-cancelled'; export { useEventGameEndSubscription } from './use-event-game-end-subscription'; +export { useEventGameLeft } from './use-event-game-left'; export { useEventMoveMadeSubscription } from './use-event-move-made-subscription'; export { useEventPlacementVerified } from './use-event-placement-verified'; +export { useEventPlayerDeleted } from './use-event-player-deleted'; export { useEventPlayerJoinedGame } from './use-event-player-joined-game'; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts index 28647d3ea..e76723699 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts @@ -26,7 +26,7 @@ export function useEventGameCancelled() { clearZkData('multi', account); resetGameState(); navigate(ROUTES.HOME); - alert.info('Admin has removed the game'); + alert.info('Game canceled. The game was terminated by the administrator.'); }; useProgramEvent({ diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts new file mode 100644 index 000000000..9728fed1f --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts @@ -0,0 +1,38 @@ +import { useNavigate } from 'react-router-dom'; +import { useProgram } from '@/app/utils/sails'; +import { useMultiplayerGame } from '../../hooks/use-multiplayer-game'; +import { useAccount, useAlert, useProgramEvent } from '@gear-js/react-hooks'; +import { clearZkData } from '@/features/zk/utils'; +import { ROUTES } from '@/app/consts'; +import { EVENT_NAME, SERVICE_NAME } from '../consts'; + +type GameLeftEvent = { + game_id: string; +}; + +export function useEventGameLeft() { + const { account } = useAccount(); + const program = useProgram(); + const alert = useAlert(); + const navigate = useNavigate(); + const { game, triggerGame, resetGameState } = useMultiplayerGame(); + + const onData = async ({ game_id }: GameLeftEvent) => { + if (!account || game?.admin !== game_id) { + return; + } + + await triggerGame(); + clearZkData('multi', account); + resetGameState(); + navigate(ROUTES.HOME); + alert.info('Game canceled. Your opponent has left the game.'); + }; + + useProgramEvent({ + program, + serviceName: SERVICE_NAME, + functionName: EVENT_NAME.SUBSCRIBE_TO_GAME_LEFT_EVENT, + onData, + }); +} diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts new file mode 100644 index 000000000..f5f9e59c1 --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts @@ -0,0 +1,49 @@ +import { useNavigate } from 'react-router-dom'; +import { useProgram } from '@/app/utils/sails'; +import { useMultiplayerGame } from '../../hooks/use-multiplayer-game'; +import { useAccount, useAlert, useProgramEvent } from '@gear-js/react-hooks'; +import { clearZkData } from '@/features/zk/utils'; +import { ROUTES } from '@/app/consts'; +import { EVENT_NAME, SERVICE_NAME } from '../consts'; +import { useState } from 'react'; + +type PlayerDeletedEvent = { + game_id: string; + removable_player: string; +}; + +export function useEventPlayerDeleted() { + const { account } = useAccount(); + const program = useProgram(); + const alert = useAlert(); + const navigate = useNavigate(); + const { game, triggerGame, resetGameState } = useMultiplayerGame(); + + const [isPlayerDeleted, setIsPlayerDeleted] = useState(false); + + const onPlayerDeletedModalClose = async () => { + if (!account) return; + + await triggerGame(); + clearZkData('multi', account); + resetGameState(); + navigate(ROUTES.HOME); + }; + + const onData = async ({ game_id, removable_player }: PlayerDeletedEvent) => { + if (!account || game?.admin !== game_id || removable_player !== account.decodedAddress) { + return; + } + + setIsPlayerDeleted(true); + }; + + useProgramEvent({ + program, + serviceName: SERVICE_NAME, + functionName: EVENT_NAME.SUBSCRIBE_TO_PLAYER_DELETED_EVENT, + onData, + }); + + return { onPlayerDeletedModalClose, isPlayerDeleted }; +} diff --git a/frontend/apps/galactic-express/src/features/session/components/game-cancelled-modal/GameCancelledModal.tsx b/frontend/apps/galactic-express/src/features/session/components/game-cancelled-modal/GameCancelledModal.tsx index d945b74e5..7f110e49b 100644 --- a/frontend/apps/galactic-express/src/features/session/components/game-cancelled-modal/GameCancelledModal.tsx +++ b/frontend/apps/galactic-express/src/features/session/components/game-cancelled-modal/GameCancelledModal.tsx @@ -10,8 +10,7 @@ type Props = { function GameCancelledModal({ admin, onClose }: Props) { return (
diff --git a/frontend/packages/signless-transactions/src/context/lib.ts b/frontend/packages/signless-transactions/src/context/lib.ts deleted file mode 100644 index 1d05d1b1c..000000000 --- a/frontend/packages/signless-transactions/src/context/lib.ts +++ /dev/null @@ -1,1335 +0,0 @@ -import { TransactionBuilder, getServiceNamePrefix, getFnNamePrefix, ZERO_ADDRESS } from 'sails-js'; -import { GearApi, decodeAddress } from '@gear-js/api'; -import { TypeRegistry } from '@polkadot/types'; - -export type ActorId = string; - -export interface VerifyingKeyBytes { - alpha_g1_beta_g2: `0x${string}`; - gamma_g2_neg_pc: `0x${string}`; - delta_g2_neg_pc: `0x${string}`; - ic: Array<`0x${string}`>; -} - -export interface Configuration { - gas_for_delete_single_game: number | string | bigint; - gas_for_delete_multiple_game: number | string | bigint; - gas_for_check_time: number | string | bigint; - delay_for_delete_single_game: number; - delay_for_delete_multiple_game: number; - delay_for_check_time: number; -} - -export interface VerificationVariables { - proof_bytes: ProofBytes; - public_input: PublicMoveInput; -} - -export interface ProofBytes { - a: `0x${string}`; - b: `0x${string}`; - c: `0x${string}`; -} - -export interface PublicMoveInput { - out: number; - hit: number; - hash: `0x${string}`; -} - -export interface PublicStartInput { - hash: `0x${string}`; -} - -export interface MultipleGameState { - admin: ActorId; - participants_data: Array<[ActorId, ParticipantInfo]>; - create_time: number | string | bigint; - start_time: number | string | bigint | null; - last_move_time: number | string | bigint; - status: Status; - bid: number | string | bigint; -} - -export interface ParticipantInfo { - name: string; - board: Array; - ship_hash: `0x${string}`; - total_shots: number; - succesfull_shots: number; -} - -export type Entity = 'Empty' | 'Unknown' | 'Occupied' | 'Ship' | 'Boom' | 'BoomShip' | 'DeadShip'; - -export type Status = - | { registration: null } - | { verificationPlacement: ActorId | null } - | { pendingVerificationOfTheMove: [ActorId, number] } - | { turn: ActorId }; - -export type MultipleUtilsStepResult = 'Missed' | 'Injured' | 'Killed'; - -export type ActionsForSession = 'playSingleGame' | 'playMultipleGame'; - -export interface Session { - key: ActorId; - expires: number | string | bigint; - allowed_actions: Array; -} - -export interface SingleGame { - player_board: Array; - ship_hash: `0x${string}`; - bot_ships: Ships; - start_time: number | string | bigint; - total_shots: number; - succesfull_shots: number; - last_move_time: number | string | bigint; - verification_requirement: number | null; -} - -export interface Ships { - ship_1: `0x${string}`; - ship_2: `0x${string}`; - ship_3: `0x${string}`; - ship_4: `0x${string}`; -} - -export interface SingleGameState { - player_board: Array; - ship_hash: `0x${string}`; - start_time: number | string | bigint; - total_shots: number; - succesfull_shots: number; - last_move_time: number | string | bigint; - verification_requirement: number | null; -} - -export type BattleshipParticipants = 'Player' | 'Bot'; - -export type SingleUtilsStepResult = 'Missed' | 'Injured' | 'Killed'; - -export class Program { - public readonly registry: TypeRegistry; - public readonly admin: Admin; - public readonly multiple: Multiple; - public readonly session: Session; - public readonly single: Single; - - constructor(public api: GearApi, public programId?: `0x${string}`) { - const types: Record = { - VerifyingKeyBytes: { - alpha_g1_beta_g2: 'Vec', - gamma_g2_neg_pc: 'Vec', - delta_g2_neg_pc: 'Vec', - ic: 'Vec>', - }, - Configuration: { - gas_for_delete_single_game: 'u64', - gas_for_delete_multiple_game: 'u64', - gas_for_check_time: 'u64', - delay_for_delete_single_game: 'u32', - delay_for_delete_multiple_game: 'u32', - delay_for_check_time: 'u32', - }, - VerificationVariables: { proof_bytes: 'ProofBytes', public_input: 'PublicMoveInput' }, - ProofBytes: { a: 'Vec', b: 'Vec', c: 'Vec' }, - PublicMoveInput: { out: 'u8', hit: 'u8', hash: 'Vec' }, - PublicStartInput: { hash: 'Vec' }, - MultipleGameState: { - admin: '[u8;32]', - participants_data: 'Vec<([u8;32], ParticipantInfo)>', - create_time: 'u64', - start_time: 'Option', - last_move_time: 'u64', - status: 'Status', - bid: 'u128', - }, - ParticipantInfo: { - name: 'String', - board: 'Vec', - ship_hash: 'Vec', - total_shots: 'u8', - succesfull_shots: 'u8', - }, - Entity: { _enum: ['Empty', 'Unknown', 'Occupied', 'Ship', 'Boom', 'BoomShip', 'DeadShip'] }, - Status: { - _enum: { - Registration: 'Null', - VerificationPlacement: 'Option<[u8;32]>', - PendingVerificationOfTheMove: '([u8;32], u8)', - Turn: '[u8;32]', - }, - }, - MultipleUtilsStepResult: { _enum: ['Missed', 'Injured', 'Killed'] }, - ActionsForSession: { _enum: ['PlaySingleGame', 'PlayMultipleGame'] }, - Session: { key: '[u8;32]', expires: 'u64', allowed_actions: 'Vec' }, - SingleGame: { - player_board: 'Vec', - ship_hash: 'Vec', - bot_ships: 'Ships', - start_time: 'u64', - total_shots: 'u8', - succesfull_shots: 'u8', - last_move_time: 'u64', - verification_requirement: 'Option', - }, - Ships: { ship_1: 'Vec', ship_2: 'Vec', ship_3: 'Vec', ship_4: 'Vec' }, - SingleGameState: { - player_board: 'Vec', - ship_hash: 'Vec', - start_time: 'u64', - total_shots: 'u8', - succesfull_shots: 'u8', - last_move_time: 'u64', - verification_requirement: 'Option', - }, - BattleshipParticipants: { _enum: ['Player', 'Bot'] }, - SingleUtilsStepResult: { _enum: ['Missed', 'Injured', 'Killed'] }, - }; - - this.registry = new TypeRegistry(); - this.registry.setKnownTypes({ types }); - this.registry.register(types); - - this.admin = new Admin(this); - this.multiple = new Multiple(this); - this.session = new Session(this); - this.single = new Single(this); - } - - newCtorFromCode( - code: Uint8Array | Buffer, - builtin_bls381: ActorId, - verification_key_for_start: VerifyingKeyBytes, - verification_key_for_move: VerifyingKeyBytes, - config: Configuration, - ): TransactionBuilder { - const builder = new TransactionBuilder( - this.api, - this.registry, - 'upload_program', - ['New', builtin_bls381, verification_key_for_start, verification_key_for_move, config], - '(String, [u8;32], VerifyingKeyBytes, VerifyingKeyBytes, Configuration)', - 'String', - code, - ); - - this.programId = builder.programId; - return builder; - } - - newCtorFromCodeId( - codeId: `0x${string}`, - builtin_bls381: ActorId, - verification_key_for_start: VerifyingKeyBytes, - verification_key_for_move: VerifyingKeyBytes, - config: Configuration, - ) { - const builder = new TransactionBuilder( - this.api, - this.registry, - 'create_program', - ['New', builtin_bls381, verification_key_for_start, verification_key_for_move, config], - '(String, [u8;32], VerifyingKeyBytes, VerifyingKeyBytes, Configuration)', - 'String', - codeId, - ); - - this.programId = builder.programId; - return builder; - } -} - -export class Admin { - constructor(private _program: Program) {} - - public changeAdmin(new_admin: ActorId): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'ChangeAdmin', new_admin], - '(String, String, [u8;32])', - 'Null', - this._program.programId, - ); - } - - public changeBuiltinAddress(new_builtin_address: ActorId): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'ChangeBuiltinAddress', new_builtin_address], - '(String, String, [u8;32])', - 'Null', - this._program.programId, - ); - } - - public changeConfiguration(configuration: Configuration): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'ChangeConfiguration', configuration], - '(String, String, Configuration)', - 'Null', - this._program.programId, - ); - } - - public changeVerificationKey( - new_vk_for_start: VerifyingKeyBytes | null, - new_vk_for_move: VerifyingKeyBytes | null, - ): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'ChangeVerificationKey', new_vk_for_start, new_vk_for_move], - '(String, String, Option, Option)', - 'Null', - this._program.programId, - ); - } - - public deleteMultipleGame(game_id: ActorId): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'DeleteMultipleGame', game_id], - '(String, String, [u8;32])', - 'Null', - this._program.programId, - ); - } - - public deleteMultipleGamesByTime(time: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'DeleteMultipleGamesByTime', time], - '(String, String, u64)', - 'Null', - this._program.programId, - ); - } - - public deleteMultipleGamesInBatches(divider: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'DeleteMultipleGamesInBatches', divider], - '(String, String, u64)', - 'Null', - this._program.programId, - ); - } - - public deleteSingleGame(player_address: ActorId): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'DeleteSingleGame', player_address], - '(String, String, [u8;32])', - 'Null', - this._program.programId, - ); - } - - public deleteSingleGames(time: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'DeleteSingleGames', time], - '(String, String, u64)', - 'Null', - this._program.programId, - ); - } - - public kill(inheritor: ActorId): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Admin', 'Kill', inheritor], - '(String, String, [u8;32])', - 'Null', - this._program.programId, - ); - } - - public async admin( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry.createType('(String, String)', ['Admin', 'Admin']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, [u8;32])', reply.payload); - return result[2].toJSON() as unknown as ActorId; - } - - public async builtin( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry.createType('(String, String)', ['Admin', 'Builtin']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, [u8;32])', reply.payload); - return result[2].toJSON() as unknown as ActorId; - } - - public async configuration( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry.createType('(String, String)', ['Admin', 'Configuration']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Configuration)', reply.payload); - return result[2].toJSON() as unknown as Configuration; - } - - public async verificationKey( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise<[VerifyingKeyBytes, VerifyingKeyBytes]> { - const payload = this._program.registry.createType('(String, String)', ['Admin', 'VerificationKey']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType( - '(String, String, (VerifyingKeyBytes, VerifyingKeyBytes))', - reply.payload, - ); - return result[2].toJSON() as unknown as [VerifyingKeyBytes, VerifyingKeyBytes]; - } - - public subscribeToGameDeletedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'GameDeleted') { - callback(null); - } - }); - } - - public subscribeToGamesDeletedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'GamesDeleted') { - callback(null); - } - }); - } - - public subscribeToAdminChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'AdminChanged') { - callback(null); - } - }); - } - - public subscribeToBuiltinAddressChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'BuiltinAddressChanged') { - callback(null); - } - }); - } - - public subscribeToVerificationKeyChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'VerificationKeyChanged') { - callback(null); - } - }); - } - - public subscribeToConfigurationChangedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'ConfigurationChanged') { - callback(null); - } - }); - } - - public subscribeToKilledEvent(callback: (data: { inheritor: ActorId }) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Admin' && getFnNamePrefix(payload) === 'Killed') { - callback( - this._program.registry - .createType('(String, String, {"inheritor":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { inheritor: ActorId }, - ); - } - }); - } -} - -export class Multiple { - constructor(private _program: Program) {} - - public cancelGame(session_for_account: ActorId | null): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'CancelGame', session_for_account], - '(String, String, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public checkOutTiming(game_id: ActorId, check_time: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'CheckOutTiming', game_id, check_time], - '(String, String, [u8;32], u64)', - 'Null', - this._program.programId, - ); - } - - public createGame(name: string, session_for_account: ActorId | null): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'CreateGame', name, session_for_account], - '(String, String, String, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public deleteGame(game_id: ActorId, create_time: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'DeleteGame', game_id, create_time], - '(String, String, [u8;32], u64)', - 'Null', - this._program.programId, - ); - } - - public deletePlayer(removable_player: ActorId, session_for_account: ActorId | null): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'DeletePlayer', removable_player, session_for_account], - '(String, String, [u8;32], Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public joinGame(game_id: ActorId, name: string, session_for_account: ActorId | null): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'JoinGame', game_id, name, session_for_account], - '(String, String, [u8;32], String, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public leaveGame(session_for_account: ActorId | null): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'LeaveGame', session_for_account], - '(String, String, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public makeMove( - game_id: ActorId, - verify_variables: VerificationVariables | null, - step: number | null, - session_for_account: ActorId | null, - ): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'MakeMove', game_id, verify_variables, step, session_for_account], - '(String, String, [u8;32], Option, Option, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public verifyPlacement( - proof: ProofBytes, - public_input: PublicStartInput, - session_for_account: ActorId | null, - game_id: ActorId, - ): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Multiple', 'VerifyPlacement', proof, public_input, session_for_account, game_id], - '(String, String, ProofBytes, PublicStartInput, Option<[u8;32]>, [u8;32])', - 'Null', - this._program.programId, - ); - } - - public async game( - player_id: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Multiple', 'Game', player_id]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as MultipleGameState | null; - } - - public async games( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise> { - const payload = this._program.registry.createType('(String, String)', ['Multiple', 'Games']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType( - '(String, String, Vec<([u8;32], MultipleGameState)>)', - reply.payload, - ); - return result[2].toJSON() as unknown as Array<[ActorId, MultipleGameState]>; - } - - public async gamesPairs( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise> { - const payload = this._program.registry.createType('(String, String)', ['Multiple', 'GamesPairs']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Vec<([u8;32], [u8;32])>)', reply.payload); - return result[2].toJSON() as unknown as Array<[ActorId, ActorId]>; - } - - public async getRemainingTime( - player_id: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Multiple', 'GetRemainingTime', player_id]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as number | string | bigint | null; - } - - public subscribeToGameCreatedEvent( - callback: (data: { player_id: ActorId }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameCreated') { - callback( - this._program.registry - .createType('(String, String, {"player_id":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { player_id: ActorId }, - ); - } - }); - } - - public subscribeToJoinedTheGameEvent( - callback: (data: { player_id: ActorId; game_id: ActorId }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'JoinedTheGame') { - callback( - this._program.registry - .createType('(String, String, {"player_id":"[u8;32]","game_id":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { player_id: ActorId; game_id: ActorId }, - ); - } - }); - } - - public subscribeToPlacementVerifiedEvent( - callback: (data: { admin: ActorId }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'PlacementVerified') { - callback( - this._program.registry - .createType('(String, String, {"admin":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { admin: ActorId }, - ); - } - }); - } - - public subscribeToGameCanceledEvent( - callback: (data: { game_id: ActorId }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameCanceled') { - callback( - this._program.registry - .createType('(String, String, {"game_id":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { game_id: ActorId }, - ); - } - }); - } - - public subscribeToGameLeftEvent(callback: (data: { game_id: ActorId }) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameLeft') { - callback( - this._program.registry - .createType('(String, String, {"game_id":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { game_id: ActorId }, - ); - } - }); - } - - public subscribeToMoveMadeEvent( - callback: (data: { - game_id: ActorId; - step: number | null; - verified_result: [number, MultipleUtilsStepResult] | null; - turn: ActorId; - }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'MoveMade') { - callback( - this._program.registry - .createType( - '(String, String, {"game_id":"[u8;32]","step":"Option","verified_result":"Option<(u8, MultipleUtilsStepResult)>","turn":"[u8;32]"})', - message.payload, - )[2] - .toJSON() as unknown as { - game_id: ActorId; - step: number | null; - verified_result: [number, MultipleUtilsStepResult] | null; - turn: ActorId; - }, - ); - } - }); - } - - public subscribeToEndGameEvent( - callback: (data: { - admin: ActorId; - winner: ActorId; - total_time: number | string | bigint; - participants_info: Array<[ActorId, ParticipantInfo]>; - last_hit: number | null; - }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'EndGame') { - callback( - this._program.registry - .createType( - '(String, String, {"admin":"[u8;32]","winner":"[u8;32]","total_time":"u64","participants_info":"Vec<([u8;32], ParticipantInfo)>","last_hit":"Option"})', - message.payload, - )[2] - .toJSON() as unknown as { - admin: ActorId; - winner: ActorId; - total_time: number | string | bigint; - participants_info: Array<[ActorId, ParticipantInfo]>; - last_hit: number | null; - }, - ); - } - }); - } - - public subscribeToGameDeletedEvent( - callback: (data: { game_id: ActorId }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'GameDeleted') { - callback( - this._program.registry - .createType('(String, String, {"game_id":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { game_id: ActorId }, - ); - } - }); - } - - public subscribeToPlayerDeletedEvent( - callback: (data: { game_id: ActorId; removable_player: ActorId }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Multiple' && getFnNamePrefix(payload) === 'PlayerDeleted') { - callback( - this._program.registry - .createType('(String, String, {"game_id":"[u8;32]","removable_player":"[u8;32]"})', message.payload)[2] - .toJSON() as unknown as { game_id: ActorId; removable_player: ActorId }, - ); - } - }); - } -} - -export class Session { - constructor(private _program: Program) {} - - public createSession( - key: ActorId, - duration: number | string | bigint, - allowed_actions: Array, - ): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Session', 'CreateSession', key, duration, allowed_actions], - '(String, String, [u8;32], u64, Vec)', - 'Null', - this._program.programId, - ); - } - - public deleteSession(): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Session', 'DeleteSession'], - '(String, String)', - 'Null', - this._program.programId, - ); - } - - public async sessionForTheAccount( - account: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Session', 'SessionForTheAccount', account]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as Session | null; - } - - public async sessions( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise> { - const payload = this._program.registry.createType('(String, String)', ['Session', 'Sessions']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Vec<([u8;32], Session)>)', reply.payload); - return result[2].toJSON() as unknown as Array<[ActorId, Session]>; - } - - public subscribeToSessionCreatedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Session' && getFnNamePrefix(payload) === 'SessionCreated') { - callback(null); - } - }); - } - - public subscribeToSessionDeletedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Session' && getFnNamePrefix(payload) === 'SessionDeleted') { - callback(null); - } - }); - } -} - -export class Single { - constructor(private _program: Program) {} - - public checkOutTiming(actor_id: ActorId, check_time: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Single', 'CheckOutTiming', actor_id, check_time], - '(String, String, [u8;32], u64)', - 'Null', - this._program.programId, - ); - } - - public deleteGame(player: ActorId, start_time: number | string | bigint): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Single', 'DeleteGame', player, start_time], - '(String, String, [u8;32], u64)', - 'Null', - this._program.programId, - ); - } - - public makeMove( - step: number | null, - verify_variables: VerificationVariables | null, - session_for_account: ActorId | null, - ): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Single', 'MakeMove', step, verify_variables, session_for_account], - '(String, String, Option, Option, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public startSingleGame( - proof: ProofBytes, - public_input: PublicStartInput, - session_for_account: ActorId | null, - ): TransactionBuilder { - if (!this._program.programId) throw new Error('Program ID is not set'); - return new TransactionBuilder( - this._program.api, - this._program.registry, - 'send_message', - ['Single', 'StartSingleGame', proof, public_input, session_for_account], - '(String, String, ProofBytes, PublicStartInput, Option<[u8;32]>)', - 'Null', - this._program.programId, - ); - } - - public async game( - player_id: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Single', 'Game', player_id]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as SingleGame | null; - } - - public async games( - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise> { - const payload = this._program.registry.createType('(String, String)', ['Single', 'Games']).toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType( - '(String, String, Vec<([u8;32], SingleGameState)>)', - reply.payload, - ); - return result[2].toJSON() as unknown as Array<[ActorId, SingleGameState]>; - } - - public async getRemainingTime( - player_id: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Single', 'GetRemainingTime', player_id]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as number | string | bigint | null; - } - - public async startTime( - player_id: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Single', 'StartTime', player_id]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as number | string | bigint | null; - } - - public async totalShots( - player_id: ActorId, - originAddress?: string, - value?: number | string | bigint, - atBlock?: `0x${string}`, - ): Promise { - const payload = this._program.registry - .createType('(String, String, [u8;32])', ['Single', 'TotalShots', player_id]) - .toHex(); - const reply = await this._program.api.message.calculateReply({ - destination: this._program.programId!, - origin: originAddress ? decodeAddress(originAddress) : ZERO_ADDRESS, - payload, - value: value || 0, - gasLimit: this._program.api.blockGasLimit.toBigInt(), - at: atBlock, - }); - if (!reply.code.isSuccess) throw new Error(this._program.registry.createType('String', reply.payload).toString()); - const result = this._program.registry.createType('(String, String, Option)', reply.payload); - return result[2].toJSON() as unknown as number | null; - } - - public subscribeToSessionCreatedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'SessionCreated') { - callback(null); - } - }); - } - - public subscribeToSingleGameStartedEvent(callback: (data: null) => void | Promise): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'SingleGameStarted') { - callback(null); - } - }); - } - - public subscribeToEndGameEvent( - callback: (data: { - player: ActorId; - winner: BattleshipParticipants; - time: number | string | bigint; - total_shots: number; - succesfull_shots: number; - last_hit: number | null; - }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'EndGame') { - callback( - this._program.registry - .createType( - '(String, String, {"player":"[u8;32]","winner":"BattleshipParticipants","time":"u64","total_shots":"u8","succesfull_shots":"u8","last_hit":"Option"})', - message.payload, - )[2] - .toJSON() as unknown as { - player: ActorId; - winner: BattleshipParticipants; - time: number | string | bigint; - total_shots: number; - succesfull_shots: number; - last_hit: number | null; - }, - ); - } - }); - } - - public subscribeToMoveMadeEvent( - callback: (data: { - player: ActorId; - step: number | null; - step_result: SingleUtilsStepResult | null; - bot_step: number | null; - }) => void | Promise, - ): Promise<() => void> { - return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { - if (!message.source.eq(this._program.programId) || !message.destination.eq(ZERO_ADDRESS)) { - return; - } - - const payload = message.payload.toHex(); - if (getServiceNamePrefix(payload) === 'Single' && getFnNamePrefix(payload) === 'MoveMade') { - callback( - this._program.registry - .createType( - '(String, String, {"player":"[u8;32]","step":"Option","step_result":"Option","bot_step":"Option"})', - message.payload, - )[2] - .toJSON() as unknown as { - player: ActorId; - step: number | null; - step_result: SingleUtilsStepResult | null; - bot_step: number | null; - }, - ); - } - }); - } -} From 40da8b98afd8add21d0d48ba9d11401bee5d9247 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 22 Aug 2024 09:18:31 +0400 Subject: [PATCH 12/17] Fix game cancel logic --- .../GameCancelledModal.module.scss | 27 -------------- .../game-cancelled-modal.tsx | 7 ++-- .../VerificationModal.module.scss | 25 ------------- .../components/multiplayer/multiplayer.tsx | 27 ++++++++++++-- .../components/registration/registration.tsx | 21 ++++++----- .../hooks/use-process-with-multiplayer.tsx | 17 ++------- .../sails/events/use-event-game-cancelled.ts | 28 +++++++++++---- .../sails/events/use-event-game-left.ts | 36 ++++++++++++++++--- .../sails/events/use-event-player-deleted.ts | 9 ++--- .../src/features/multiplayer/utils.ts | 4 +++ 10 files changed, 103 insertions(+), 98 deletions(-) create mode 100644 frontend/apps/battleship-zk/src/features/multiplayer/utils.ts diff --git a/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/GameCancelledModal.module.scss b/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/GameCancelledModal.module.scss index 3e885f58d..e837c7aee 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/GameCancelledModal.module.scss +++ b/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/GameCancelledModal.module.scss @@ -1,36 +1,9 @@ -@use '@/assets/styles/utils' as *; - .content { display: flex; flex-direction: column; gap: 24px; } -.gameInfo { - display: flex; - flex-direction: column; - gap: 16px; - background: #ffffff; - padding: 16px; - - .line { - display: flex; - justify-content: space-between; - align-items: baseline; - - p { - white-space: nowrap; - } - - hr { - margin: 0px 10px; - border: 0; - width: 100%; - border-bottom: 1px solid #dadada; - } - } -} - .buttons { display: flex; align-items: center; diff --git a/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/game-cancelled-modal.tsx b/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/game-cancelled-modal.tsx index b70a73767..58f91a652 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/game-cancelled-modal.tsx +++ b/frontend/apps/battleship-zk/src/features/game/components/game-cancelled-modal/game-cancelled-modal.tsx @@ -5,13 +5,12 @@ import { ModalBottom } from '@/components/ui/modal'; import styles from './GameCancelledModal.module.scss'; type Props = { - isOpen: boolean; text: string; onClose: () => void; }; -export default function GameCancelledModal({ isOpen, text, onClose }: Props) { - return isOpen ? ( +export default function GameCancelledModal({ text, onClose }: Props) { + return (
{text} @@ -20,5 +19,5 @@ export default function GameCancelledModal({ isOpen, text, onClose }: Props) {
- ) : null; + ); } diff --git a/frontend/apps/battleship-zk/src/features/game/components/verification-modal/VerificationModal.module.scss b/frontend/apps/battleship-zk/src/features/game/components/verification-modal/VerificationModal.module.scss index 3e885f58d..9c9ad83ed 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/verification-modal/VerificationModal.module.scss +++ b/frontend/apps/battleship-zk/src/features/game/components/verification-modal/VerificationModal.module.scss @@ -6,31 +6,6 @@ gap: 24px; } -.gameInfo { - display: flex; - flex-direction: column; - gap: 16px; - background: #ffffff; - padding: 16px; - - .line { - display: flex; - justify-content: space-between; - align-items: baseline; - - p { - white-space: nowrap; - } - - hr { - margin: 0px 10px; - border: 0; - width: 100%; - border-bottom: 1px solid #dadada; - } - } -} - .buttons { display: flex; align-items: center; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/multiplayer/multiplayer.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/multiplayer/multiplayer.tsx index 17c9b10ed..9261a8bcc 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/multiplayer/multiplayer.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/multiplayer/multiplayer.tsx @@ -8,8 +8,16 @@ import { ROUTES } from '@/app/consts'; import { useShips } from '@/features/zk/hooks/use-ships'; import { decodeAddress } from '@gear-js/api'; import { usePending } from '@/features/game/hooks'; -import { useEventPlacementVerified, useEventGameCancelled, useEventMoveMadeSubscription } from '../../sails/events'; +import { GameCancelledModal } from '@/features/game/components'; +import { + useEventPlacementVerified, + useEventGameCancelled, + useEventMoveMadeSubscription, + useEventPlayerDeleted, + useEventGameLeft, +} from '../../sails/events'; import { useMultiplayerGame, useProcessWithMultiplayer, useArrangementWithMultiplayer } from '../../hooks'; +import { getIsPlacementStatus } from '../../utils'; import styles from './Multiplayer.module.scss'; export function Multiplayer() { @@ -32,11 +40,13 @@ export function Multiplayer() { const [savedPlayerBoard, setSavedPlayerBoard] = useState(); useEventPlacementVerified(); - useEventGameCancelled(); + const { isGameCancelled, onGameCancelled } = useEventGameCancelled(); useEventMoveMadeSubscription(); + const { isPlayerDeleted, onPlayerDeleted } = useEventPlayerDeleted(); + const { isGameLeft, onGameLeft } = useEventGameLeft(); const playerInfo = game?.participants_data.find((item) => decodeAddress(item[0]) === account?.decodedAddress)?.[1]; - const isPlacementStatus = Object.keys(game?.status || {})[0] === 'verificationPlacement'; + const isPlacementStatus = getIsPlacementStatus(game); const handleCloseModal = () => { navigate(ROUTES.HOME); @@ -68,6 +78,17 @@ export function Multiplayer() {
)} + + {isPlayerDeleted && ( + + )} + {isGameCancelled && ( + + )} + {isGameLeft && } ) : ( { navigate(ROUTES.GAME); @@ -174,11 +174,16 @@ export function Registration() { )} - + {isPlayerDeleted && ( + + )} + {isGameCancelled && ( + + )} + {isGameLeft && } ); } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx index d4ee52fc6..27beefcfb 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/hooks/use-process-with-multiplayer.tsx @@ -53,22 +53,9 @@ export const useProcessWithMultiplayer = () => { setPending(true); - if (game?.admin === account?.decodedAddress) { - try { - const transaction = await cancelGameMessage(); - const { response } = await transaction.signAndSend(); - - await response(); - } catch (err) { - } finally { - setPending(false); - } - - return; - } - try { - const transaction = await leaveGameMessage(); + const getTransaction = game?.admin === account?.decodedAddress ? cancelGameMessage : leaveGameMessage; + const transaction = await getTransaction(); const { response } = await transaction.signAndSend(); await response(); diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts index e76723699..73ef92442 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-cancelled.ts @@ -1,10 +1,11 @@ import { useNavigate } from 'react-router-dom'; import { useProgram } from '@/app/utils/sails'; import { useMultiplayerGame } from '../../hooks/use-multiplayer-game'; -import { useAccount, useAlert, useProgramEvent } from '@gear-js/react-hooks'; +import { useAccount, useProgramEvent } from '@gear-js/react-hooks'; import { clearZkData } from '@/features/zk/utils'; import { ROUTES } from '@/app/consts'; import { EVENT_NAME, SERVICE_NAME } from '../consts'; +import { useState } from 'react'; type GameCancelledEvent = { game_id: string; @@ -13,20 +14,31 @@ type GameCancelledEvent = { export function useEventGameCancelled() { const { account } = useAccount(); const program = useProgram(); - const alert = useAlert(); const navigate = useNavigate(); const { game, triggerGame, resetGameState } = useMultiplayerGame(); - const onData = async ({ game_id }: GameCancelledEvent) => { - if (!account || game?.admin !== game_id) { - return; - } + const [isGameCancelled, setIsGameCancelled] = useState(false); + + const onGameCancelled = async () => { + if (!account) return; await triggerGame(); clearZkData('multi', account); resetGameState(); + setIsGameCancelled(false); navigate(ROUTES.HOME); - alert.info('Game canceled. The game was terminated by the administrator.'); + }; + + const onData = async ({ game_id }: GameCancelledEvent) => { + if (!account || game?.admin !== game_id) { + return; + } + + if (game?.admin === account?.decodedAddress) { + onGameCancelled(); + } else { + setIsGameCancelled(true); + } }; useProgramEvent({ @@ -35,4 +47,6 @@ export function useEventGameCancelled() { functionName: EVENT_NAME.SUBSCRIBE_TO_GAME_CANCELED_EVENT, onData, }); + + return { isGameCancelled, onGameCancelled }; } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts index 9728fed1f..a9e3b8a8c 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts @@ -5,6 +5,8 @@ import { useAccount, useAlert, useProgramEvent } from '@gear-js/react-hooks'; import { clearZkData } from '@/features/zk/utils'; import { ROUTES } from '@/app/consts'; import { EVENT_NAME, SERVICE_NAME } from '../consts'; +import { useState } from 'react'; +import { getIsPlacementStatus } from '../../utils'; type GameLeftEvent = { game_id: string; @@ -17,16 +19,38 @@ export function useEventGameLeft() { const navigate = useNavigate(); const { game, triggerGame, resetGameState } = useMultiplayerGame(); + const [isGameLeft, setIsGameLeft] = useState(false); + + const onGameLeft = async () => { + if (!account) return; + + await triggerGame(); + navigate(ROUTES.HOME); + }; + const onData = async ({ game_id }: GameLeftEvent) => { + console.log('! GameLeft myacc:', account?.decodedAddress, game?.admin); + if (!account || game?.admin !== game_id) { return; } - await triggerGame(); - clearZkData('multi', account); - resetGameState(); - navigate(ROUTES.HOME); - alert.info('Game canceled. Your opponent has left the game.'); + if (game?.admin === account?.decodedAddress) { + console.log(2); + if (getIsPlacementStatus(game)) { + setIsGameLeft(true); + } else { + alert.info('Your opponent has left the game.'); + onGameLeft(); + } + } else { + await triggerGame(); + clearZkData('multi', account); + resetGameState(); + setIsGameLeft(false); + navigate(ROUTES.HOME); + console.log(1); + } }; useProgramEvent({ @@ -35,4 +59,6 @@ export function useEventGameLeft() { functionName: EVENT_NAME.SUBSCRIBE_TO_GAME_LEFT_EVENT, onData, }); + + return { isGameLeft, onGameLeft }; } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts index f5f9e59c1..3872998dc 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts @@ -1,7 +1,7 @@ import { useNavigate } from 'react-router-dom'; import { useProgram } from '@/app/utils/sails'; import { useMultiplayerGame } from '../../hooks/use-multiplayer-game'; -import { useAccount, useAlert, useProgramEvent } from '@gear-js/react-hooks'; +import { useAccount, useProgramEvent } from '@gear-js/react-hooks'; import { clearZkData } from '@/features/zk/utils'; import { ROUTES } from '@/app/consts'; import { EVENT_NAME, SERVICE_NAME } from '../consts'; @@ -15,22 +15,23 @@ type PlayerDeletedEvent = { export function useEventPlayerDeleted() { const { account } = useAccount(); const program = useProgram(); - const alert = useAlert(); const navigate = useNavigate(); const { game, triggerGame, resetGameState } = useMultiplayerGame(); const [isPlayerDeleted, setIsPlayerDeleted] = useState(false); - const onPlayerDeletedModalClose = async () => { + const onPlayerDeleted = async () => { if (!account) return; await triggerGame(); clearZkData('multi', account); resetGameState(); + setIsPlayerDeleted(false); navigate(ROUTES.HOME); }; const onData = async ({ game_id, removable_player }: PlayerDeletedEvent) => { + console.log('! PlayerDeleted'); if (!account || game?.admin !== game_id || removable_player !== account.decodedAddress) { return; } @@ -45,5 +46,5 @@ export function useEventPlayerDeleted() { onData, }); - return { onPlayerDeletedModalClose, isPlayerDeleted }; + return { onPlayerDeleted, isPlayerDeleted }; } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/utils.ts b/frontend/apps/battleship-zk/src/features/multiplayer/utils.ts new file mode 100644 index 000000000..14ffde785 --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/multiplayer/utils.ts @@ -0,0 +1,4 @@ +import { MultipleGameState } from '@/app/utils/sails/lib/lib'; + +export const getIsPlacementStatus = (game: MultipleGameState | null | undefined) => + Object.keys(game?.status || {})[0] === 'verificationPlacement'; From 2d49dabf12ebf44fa572b0f4f5ef056bde4887e6 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 22 Aug 2024 09:26:24 +0400 Subject: [PATCH 13/17] add player has joined notification --- .../multiplayer/sails/events/use-event-game-left.ts | 3 --- .../sails/events/use-event-player-deleted.ts | 1 - .../sails/events/use-event-player-joined-game.ts | 10 ++++++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts index a9e3b8a8c..f45d25be4 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-game-left.ts @@ -29,14 +29,12 @@ export function useEventGameLeft() { }; const onData = async ({ game_id }: GameLeftEvent) => { - console.log('! GameLeft myacc:', account?.decodedAddress, game?.admin); if (!account || game?.admin !== game_id) { return; } if (game?.admin === account?.decodedAddress) { - console.log(2); if (getIsPlacementStatus(game)) { setIsGameLeft(true); } else { @@ -49,7 +47,6 @@ export function useEventGameLeft() { resetGameState(); setIsGameLeft(false); navigate(ROUTES.HOME); - console.log(1); } }; diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts index 3872998dc..64e30b527 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-deleted.ts @@ -31,7 +31,6 @@ export function useEventPlayerDeleted() { }; const onData = async ({ game_id, removable_player }: PlayerDeletedEvent) => { - console.log('! PlayerDeleted'); if (!account || game?.admin !== game_id || removable_player !== account.decodedAddress) { return; } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-joined-game.ts b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-joined-game.ts index dd57ce4a6..02fca39e5 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-joined-game.ts +++ b/frontend/apps/battleship-zk/src/features/multiplayer/sails/events/use-event-player-joined-game.ts @@ -1,4 +1,4 @@ -import { useProgramEvent } from '@gear-js/react-hooks'; +import { useAccount, useAlert, useProgramEvent } from '@gear-js/react-hooks'; import { useProgram } from '@/app/utils/sails'; import { useMultiplayerGame } from '../../hooks/use-multiplayer-game'; import { EVENT_NAME, SERVICE_NAME } from '../consts'; @@ -11,10 +11,16 @@ type PlayerJoinedEvent = { export function useEventPlayerJoinedGame() { const { game, triggerGame } = useMultiplayerGame(); const program = useProgram(); + const alert = useAlert(); + const { account } = useAccount(); - const onData = ({ game_id }: PlayerJoinedEvent) => { + const onData = ({ game_id, player_id }: PlayerJoinedEvent) => { if (game?.admin === game_id) { triggerGame(); + + if (player_id !== account?.decodedAddress) { + alert.info('The player has joined.'); + } } }; From 4a670c50f713f2458c24c5052ee840fbe06b4b9e Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 22 Aug 2024 11:37:40 +0400 Subject: [PATCH 14/17] Fix ships map --- .../game/components/map/map.module.scss | 67 ++++++------------- .../src/features/game/components/map/map.tsx | 33 ++------- .../features/game/components/map/mapEnemy.tsx | 2 +- .../battleship-zk/src/features/game/utils.ts | 4 +- .../src/features/zk/hooks/use-ships.ts | 15 +++-- 5 files changed, 36 insertions(+), 85 deletions(-) diff --git a/frontend/apps/battleship-zk/src/features/game/components/map/map.module.scss b/frontend/apps/battleship-zk/src/features/game/components/map/map.module.scss index abd291d7a..98554d054 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/map/map.module.scss +++ b/frontend/apps/battleship-zk/src/features/game/components/map/map.module.scss @@ -55,7 +55,7 @@ height: 24px; & path { - fill: #808080; + fill: #eb5757; } &Enemy { @@ -114,35 +114,29 @@ .hitShip { border: 1px solid #00000033; - background: #cdcdcd; + background: #f5abab; &Enemy { background: transparent; } } -.hitEmpty { - width: 4px; - height: 4px; - border-radius: 50px; - background: #d9d9d9; - - &Enemy { - position: relative; - width: 8px; - height: 8px; - background: #eb5757; - - &::before { - content: ''; - position: absolute; - top: -4px; - left: -4px; - width: 16px; - height: 16px; - border-radius: 50px; - background: rgba(235, 87, 87, 0.5); - } +.hitCircle { + position: relative; + width: 8px; + height: 8px; + border-radius: 50%; + background: #eb5757; + + &::before { + content: ''; + position: absolute; + top: -4px; + left: -4px; + width: 16px; + height: 16px; + border-radius: 50%; + background: rgba(235, 87, 87, 0.5); } } @@ -152,7 +146,7 @@ .deadShip { border: 1px solid #00000033; - background: #ffe8e8; + background: #f5abab; &Enemy { background: transparent; @@ -164,8 +158,6 @@ width: 32px; height: 32px; position: absolute; - top: -1; - left: -1.5; display: flex; align-items: center; align-content: center; @@ -173,23 +165,13 @@ } :root { - --circle-color: #d9d9d9; --size-to: 36px; } .circleEl { width: 0; height: 0; - background: transparent; - border: 2px solid var(--circle-color); - border-radius: 50%; - animation: go 3s ease-out infinite; -} - -.circleHit { - width: 0; - height: 0; - background: #00ffc4; + border: 2px solid #eb5757; border-radius: 50%; animation: pulse 3s ease-out infinite; } @@ -202,15 +184,6 @@ animation-delay: 2s; } -@keyframes go { - 100% { - width: var(--size-to); - height: var(--size-to); - box-shadow: 0 0 15px var(--circle-color); - opacity: 0; - } -} - @keyframes pulse { 0% { opacity: 0.8; diff --git a/frontend/apps/battleship-zk/src/features/game/components/map/map.tsx b/frontend/apps/battleship-zk/src/features/game/components/map/map.tsx index 8f0b2e380..08fa4c86a 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/map/map.tsx +++ b/frontend/apps/battleship-zk/src/features/game/components/map/map.tsx @@ -12,7 +12,7 @@ export default function Map({ sizeBlock = 64, shipStatusArray, lastHit }: Props) const numRows = 5; const numCols = 5; - const renderEmptyCirculeAnimation = () => { + const renderHitCirculeAnimation = () => { return ( <>
@@ -28,22 +28,6 @@ export default function Map({ sizeBlock = 64, shipStatusArray, lastHit }: Props) ); }; - const renderHitCirculeAnimation = () => { - return ( - <> -
- -
-
- -
-
- -
- - ); - }; - const renderCell = (row: number, col: number) => { const cellIndex = row * numCols + col; const cellStatus = shipStatusArray[cellIndex]; @@ -71,18 +55,9 @@ export default function Map({ sizeBlock = 64, shipStatusArray, lastHit }: Props) return (
- {isHit && !isHitShips && !isDeadShips && ( - <> -
- {lastHit === cellIndex && renderEmptyCirculeAnimation()} - - )} - {(isHitShips || isDeadShips) && ( - <> - - {lastHit === cellIndex && renderHitCirculeAnimation()} - - )} + {(isHit || isHitShips || (isDeadShips && lastHit === cellIndex)) &&
} + {isDeadShips && lastHit !== cellIndex && } + {lastHit === cellIndex && renderHitCirculeAnimation()}
); }; diff --git a/frontend/apps/battleship-zk/src/features/game/components/map/mapEnemy.tsx b/frontend/apps/battleship-zk/src/features/game/components/map/mapEnemy.tsx index 88369d509..dd917b737 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/map/mapEnemy.tsx +++ b/frontend/apps/battleship-zk/src/features/game/components/map/mapEnemy.tsx @@ -156,7 +156,7 @@ export default function MapEnemy({ className={clsx(cellClassName, styles.blockEnemy, isDisabledCell && styles.blockDisabled)} style={cellStyle} onClick={() => handleCellClick(cellIndex)}> - {isHit && !isDeadShips && !isHitShips &&
} + {isHit && !isDeadShips && !isHitShips &&
} {isDeadShips && !!deadShips[cellIndex] && handleRenderDeadShip(deadShips[cellIndex])} {(isDeadShips || isHitShips) && ( <> diff --git a/frontend/apps/battleship-zk/src/features/game/utils.ts b/frontend/apps/battleship-zk/src/features/game/utils.ts index 275ab1008..b287a0451 100644 --- a/frontend/apps/battleship-zk/src/features/game/utils.ts +++ b/frontend/apps/battleship-zk/src/features/game/utils.ts @@ -117,7 +117,7 @@ export const defineDeadShip = (i: number, board: string[]) => { export function checkDeadShip(index: number, board: string[]): boolean { const boardSize = 5; - if (board[index] !== 'BoomShip') { + if (!['BoomShip', 'DeadShip'].includes(board[index])) { return false; } @@ -148,7 +148,7 @@ export function checkDeadShip(index: number, board: string[]): boolean { if (board[newIndex] === 'Ship') { return false; } - if (board[newIndex] === 'BoomShip' && !checkNeighbors(newIndex)) { + if (['BoomShip', 'DeadShip'].includes(board[newIndex]) && !checkNeighbors(newIndex)) { return false; } } diff --git a/frontend/apps/battleship-zk/src/features/zk/hooks/use-ships.ts b/frontend/apps/battleship-zk/src/features/zk/hooks/use-ships.ts index f1c32fb31..8c102eb04 100644 --- a/frontend/apps/battleship-zk/src/features/zk/hooks/use-ships.ts +++ b/frontend/apps/battleship-zk/src/features/zk/hooks/use-ships.ts @@ -139,19 +139,22 @@ export function useShips() { }; }; - const updatePlayerBoard = (gameType: GameType, bot_step: number) => { - const board = getBoard(gameType, 'player'); + const updatePlayerBoard = (gameType: GameType, enemy_step: number) => { + let board = getBoard(gameType, 'player'); if (!board) { return; } - if (board[bot_step] === 'Empty') { - board[bot_step] = 'Boom'; + if (board[enemy_step] === 'Empty') { + board[enemy_step] = 'Boom'; } - if (board[bot_step] === 'Ship') { - board[bot_step] = 'BoomShip'; + if (board[enemy_step] === 'Ship') { + board[enemy_step] = 'BoomShip'; + if (checkDeadShip(enemy_step, board)) { + board = defineDeadShip(enemy_step, board); + } } setBoard(gameType, 'player', board); From f88e67982ef2e04c345f5dedd61bda4cf20065ee Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Thu, 22 Aug 2024 18:10:19 +0400 Subject: [PATCH 15/17] fix build --- .../signless-transactions/src/hooks/use-create-base-session.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts index 74be64570..c83f1492e 100644 --- a/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts +++ b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts @@ -1,7 +1,6 @@ import { HexString, IVoucherDetails } from '@gear-js/api'; import { Account, useAccount, useAlert, useApi, useBalanceFormat } from '@gear-js/react-hooks'; import { SubmittableExtrinsic } from '@polkadot/api/types'; -import { web3FromSource } from '@polkadot/extension-dapp'; import { KeyringPair } from '@polkadot/keyring/types'; import { ISubmittableResult } from '@polkadot/types/types'; @@ -48,7 +47,7 @@ function useCreateBaseSession(programId: HexString) { const onError = (message: string) => alert.error(message); const signHex = async (account: Account, hexToSign: `0x${string}`) => { - const { signer } = await web3FromSource(account.meta.source); + const { signer } = account; const { signRaw } = signer; console.log('SIGNATURE:'); From 392131ede6c22d818a034ee41a3c91fdb7962ccc Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Fri, 23 Aug 2024 12:02:25 +0400 Subject: [PATCH 16/17] Add voucher expired modal --- .../VoucherExpiredModal.module.scss | 19 +++++++++ .../voucher-expired-modal/index.tsx | 3 ++ .../voucher-expired-modal.tsx | 39 +++++++++++++++++++ .../hooks/use-process-with-singleplayer.tsx | 2 +- .../apps/battleship-zk/src/pages/game.tsx | 3 ++ .../src/context/consts.ts | 1 + .../src/context/index.tsx | 18 ++++++++- .../gasless-transactions/src/context/types.ts | 1 + 8 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/VoucherExpiredModal.module.scss create mode 100644 frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/index.tsx create mode 100644 frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx diff --git a/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/VoucherExpiredModal.module.scss b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/VoucherExpiredModal.module.scss new file mode 100644 index 000000000..9c9ad83ed --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/VoucherExpiredModal.module.scss @@ -0,0 +1,19 @@ +@use '@/assets/styles/utils' as *; + +.content { + display: flex; + flex-direction: column; + gap: 24px; +} + +.buttons { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + + & button { + width: 100%; + padding: 16px 10px; + } +} diff --git a/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/index.tsx b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/index.tsx new file mode 100644 index 000000000..4a8908ece --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/index.tsx @@ -0,0 +1,3 @@ +import VoucherExpiredModal from './voucher-expired-modal'; + +export { VoucherExpiredModal }; diff --git a/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx new file mode 100644 index 000000000..5a4b19203 --- /dev/null +++ b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react'; +import { Text } from '@/components/ui/text'; +import { Button } from '@gear-js/vara-ui'; +import { useGaslessTransactions } from '@dapps-frontend/ez-transactions'; +import { useCountdown } from '@dapps-frontend/hooks'; +import { ModalBottom } from '@/components/ui/modal'; +import styles from './VoucherExpiredModal.module.scss'; + +type Props = {}; + +export default function VoucherExpiredModal({}: Props) { + const { expireTimestamp, setIsEnabled } = useGaslessTransactions(); + const [isOpen, setIsOpen] = useState(true); + + const countdown = useCountdown(expireTimestamp || undefined); + const isVoucherExpired = countdown === 0; + + useEffect(() => { + if (isVoucherExpired) { + setIsOpen(true); + setIsEnabled(false); + } + }, [isVoucherExpired]); + + return ( + <> + {isOpen && isVoucherExpired && ( + setIsOpen(false)}> +
+ Your voucher has expired and couldn't be used. +
+
+
+
+ )} + + ); +} diff --git a/frontend/apps/battleship-zk/src/features/singleplayer/hooks/use-process-with-singleplayer.tsx b/frontend/apps/battleship-zk/src/features/singleplayer/hooks/use-process-with-singleplayer.tsx index 163e6597f..adcd0e0b0 100644 --- a/frontend/apps/battleship-zk/src/features/singleplayer/hooks/use-process-with-singleplayer.tsx +++ b/frontend/apps/battleship-zk/src/features/singleplayer/hooks/use-process-with-singleplayer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ROUTES } from '@/app/consts'; import { useMoveTransaction, usePending } from '@/features/game/hooks'; diff --git a/frontend/apps/battleship-zk/src/pages/game.tsx b/frontend/apps/battleship-zk/src/pages/game.tsx index 8336fa568..444193d42 100644 --- a/frontend/apps/battleship-zk/src/pages/game.tsx +++ b/frontend/apps/battleship-zk/src/pages/game.tsx @@ -2,6 +2,7 @@ import { useInitMultiplayerGame } from '@/features/multiplayer/hooks'; import { Singleplayer } from '@/features/singleplayer/components/singleplayer'; import { Multiplayer } from '@/features/multiplayer/components/multiplayer'; import styles from './game.module.scss'; +import { VoucherExpiredModal } from '@/features/game/components/voucher-expired-modal'; export default function GamePage() { const { isActiveGame: isActiveMultiplayer } = useInitMultiplayerGame(); @@ -11,6 +12,8 @@ export default function GamePage() {
{isActiveMultiplayer ? : }
+ + ); } diff --git a/frontend/packages/gasless-transactions/src/context/consts.ts b/frontend/packages/gasless-transactions/src/context/consts.ts index 45336e726..2bed1bb5e 100644 --- a/frontend/packages/gasless-transactions/src/context/consts.ts +++ b/frontend/packages/gasless-transactions/src/context/consts.ts @@ -4,6 +4,7 @@ export const DEFAULT_GASLESS_CONTEXT = { isEnabled: false, isActive: false, voucherStatus: null, + expireTimestamp: null, requestVoucher: async (): Promise<`0x${string}`> => '0x', setIsEnabled: () => {}, }; diff --git a/frontend/packages/gasless-transactions/src/context/index.tsx b/frontend/packages/gasless-transactions/src/context/index.tsx index cc4c1604d..69278fe6f 100644 --- a/frontend/packages/gasless-transactions/src/context/index.tsx +++ b/frontend/packages/gasless-transactions/src/context/index.tsx @@ -30,6 +30,11 @@ function GaslessTransactionsProvider({ backendAddress, programId, voucherLimit, const isActive = Boolean(accountAddress && voucherId); const [voucherStatus, setVoucherStatus] = useState(null); + const expireTimestamp = useMemo( + () => (voucherId && voucherStatus ? Date.now() + voucherStatus.duration * 1000 : null), + [voucherId, voucherStatus], + ); + const requestVoucher = async (_accountAddress: string) => withLoading( getVoucherId(backendAddress, _accountAddress, programId).then((result) => { @@ -73,9 +78,18 @@ function GaslessTransactionsProvider({ backendAddress, programId, voucherLimit, }, [account]); const value = useMemo( - () => ({ voucherId, isLoading, isEnabled, isActive, voucherStatus, requestVoucher, setIsEnabled }), + () => ({ + voucherId, + isLoading, + isEnabled, + isActive, + voucherStatus, + expireTimestamp, + requestVoucher, + setIsEnabled, + }), // eslint-disable-next-line react-hooks/exhaustive-deps - [voucherId, isLoading, isEnabled, isActive, voucherStatus?.id], + [voucherId, isLoading, isEnabled, isActive, voucherStatus?.id, expireTimestamp], ); return {children}; diff --git a/frontend/packages/gasless-transactions/src/context/types.ts b/frontend/packages/gasless-transactions/src/context/types.ts index bf9c17026..4ac795f8e 100644 --- a/frontend/packages/gasless-transactions/src/context/types.ts +++ b/frontend/packages/gasless-transactions/src/context/types.ts @@ -6,6 +6,7 @@ export type GaslessContext = { isEnabled: boolean; isActive: boolean; voucherStatus: VoucherStatus | null; + expireTimestamp: number | null; requestVoucher: (accountAddress: string) => Promise<`0x${string}`>; setIsEnabled: (value: boolean) => void; }; From a9a7f0b4744db4d750f6e1becc0adeae77e35bd9 Mon Sep 17 00:00:00 2001 From: Vadim Tokar Date: Fri, 23 Aug 2024 15:06:40 +0400 Subject: [PATCH 17/17] Fix review --- .../src/components/ui/modal/Modal.tsx | 1 + .../voucher-expired-modal.tsx | 25 ++++++++----------- .../create-game-form/create-game-form.tsx | 1 - .../game-found-modal/game-found-modal.tsx | 1 - .../apps/battleship-zk/src/pages/game.tsx | 2 +- .../src/hooks/use-create-base-session.ts | 6 ----- 6 files changed, 13 insertions(+), 23 deletions(-) diff --git a/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx b/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx index c4878cd4a..ffa3b4fea 100644 --- a/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx +++ b/frontend/apps/battleship-zk/src/components/ui/modal/Modal.tsx @@ -15,6 +15,7 @@ type Props = React.PropsWithChildren & { }; onClose: () => void; closeOnMissclick?: boolean; + // hacky fix cuz the signless modal was not displaying above the dialog opened via showModal showModalMode?: boolean; }; diff --git a/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx index 5a4b19203..4101dd814 100644 --- a/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx +++ b/frontend/apps/battleship-zk/src/features/game/components/voucher-expired-modal/voucher-expired-modal.tsx @@ -6,9 +6,7 @@ import { useCountdown } from '@dapps-frontend/hooks'; import { ModalBottom } from '@/components/ui/modal'; import styles from './VoucherExpiredModal.module.scss'; -type Props = {}; - -export default function VoucherExpiredModal({}: Props) { +export default function VoucherExpiredModal() { const { expireTimestamp, setIsEnabled } = useGaslessTransactions(); const [isOpen, setIsOpen] = useState(true); @@ -23,17 +21,16 @@ export default function VoucherExpiredModal({}: Props) { }, [isVoucherExpired]); return ( - <> - {isOpen && isVoucherExpired && ( - setIsOpen(false)}> -
- Your voucher has expired and couldn't be used. -
-
+ isOpen && + isVoucherExpired && ( + setIsOpen(false)}> +
+ Your voucher has expired and couldn't be used. +
+
- - )} - +
+
+ ) ); } diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx index 408e2e019..56ce6646a 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/create-game-form/create-game-form.tsx @@ -36,7 +36,6 @@ function CreateGameForm({ onCancel }: Props) { const existentialDeposit = Number(getFormattedBalanceValue(api?.existentialDeposit.toNumber() || 0).toFixed()); const { getChainBalanceValue } = useBalanceFormat(); - // ! TODO: check this logic const createForm = useForm({ initialValues: { fee: 0, diff --git a/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx b/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx index af5466e43..f5c0935fa 100644 --- a/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx +++ b/frontend/apps/battleship-zk/src/features/multiplayer/components/game-found-modal/game-found-modal.tsx @@ -62,7 +62,6 @@ function GameFoundModal({ entryFee, onSubmit, onClose }: Props) { heading="The game has been found" className={{ wrapper: styles.modalWrapper, modal: styles.modal }} onClose={onClose} - // hacky fix cuz the signless modal was not displaying above the dialog opened via showModal showModalMode={false}>

diff --git a/frontend/apps/battleship-zk/src/pages/game.tsx b/frontend/apps/battleship-zk/src/pages/game.tsx index 444193d42..78c9bdf03 100644 --- a/frontend/apps/battleship-zk/src/pages/game.tsx +++ b/frontend/apps/battleship-zk/src/pages/game.tsx @@ -1,8 +1,8 @@ import { useInitMultiplayerGame } from '@/features/multiplayer/hooks'; import { Singleplayer } from '@/features/singleplayer/components/singleplayer'; import { Multiplayer } from '@/features/multiplayer/components/multiplayer'; -import styles from './game.module.scss'; import { VoucherExpiredModal } from '@/features/game/components/voucher-expired-modal'; +import styles from './game.module.scss'; export default function GamePage() { const { isActiveGame: isActiveMultiplayer } = useInitMultiplayerGame(); diff --git a/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts index c83f1492e..f05c3af78 100644 --- a/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts +++ b/frontend/packages/signless-transactions/src/hooks/use-create-base-session.ts @@ -50,12 +50,6 @@ function useCreateBaseSession(programId: HexString) { const { signer } = account; const { signRaw } = signer; - console.log('SIGNATURE:'); - console.log('ACCOUNT', account); - console.log('SIGNER', signer); - console.log('signRaw', signRaw); - console.log('HEXtoSIGN: ', hexToSign); - if (!signRaw) { throw new Error('signRaw is not a function'); }