diff --git a/.github/workflows/Release-tictac.yml b/.github/workflows/Release-tictac.yml index 0e021cfc9..fcbcfe33e 100644 --- a/.github/workflows/Release-tictac.yml +++ b/.github/workflows/Release-tictac.yml @@ -71,7 +71,8 @@ jobs: tags: ${{ needs.prepair.outputs.image_name }} build-args: | VITE_NODE_ADDRESS=${{ secrets.VITE_NODE_ADDRESS }} - VITE_CONTRACT_ADDRESS=${{ secrets.VITE_CONTRACT_ADDRESS_TIC_TAC }} + VITE_DNS_API_URL=${{ secrets.VITE_DNS_API_URL }} + VITE_DNS_NAME=${{ secrets.VITE_DNS_NAME_TIC_TAC }} VITE_GASLESS_BACKEND_ADDRESS=${{ secrets.VITE_GASLESS_BACKEND_ADDRESS }} VITE_SENTRY_DSN=${{ secrets.REACT_SENTRY_DSN_TIC_TAC }} diff --git a/.github/workflows/STG-tictac.yml b/.github/workflows/STG-tictac.yml index 5c7f8c71c..189d0df1f 100644 --- a/.github/workflows/STG-tictac.yml +++ b/.github/workflows/STG-tictac.yml @@ -76,7 +76,8 @@ jobs: tags: ${{ needs.prepair.outputs.image_name }} build-args: | VITE_NODE_ADDRESS=${{ secrets.VITE_NODE_ADDRESS }} - VITE_CONTRACT_ADDRESS=${{ secrets.VITE_CONTRACT_ADDRESS_TIC_TAC }} + VITE_DNS_API_URL=${{ secrets.VITE_DNS_API_URL }} + VITE_DNS_NAME=${{ secrets.VITE_DNS_NAME_TIC_TAC }} VITE_GASLESS_BACKEND_ADDRESS=${{ secrets.VITE_GASLESS_BACKEND_ADDRESS_TIC_TAC }} VITE_SENTRY_DSN=${{ secrets.REACT_SENTRY_DSN_TIC_TAC }} diff --git a/frontend/apps/tic-tac-toe/.env.example b/frontend/apps/tic-tac-toe/.env.example index eb97a99cd..057d8caee 100644 --- a/frontend/apps/tic-tac-toe/.env.example +++ b/frontend/apps/tic-tac-toe/.env.example @@ -1,6 +1,7 @@ VITE_NODE_ADDRESS= -VITE_CONTRACT_ADDRESS= VITE_GASLESS_BACKEND_ADDRESS= +VITE_DNS_API_URL= +VITE_DNS_NAME= # optional, specify sentry dsn and targetted domain for error tracking # if domain is not specified, localhost is used by default diff --git a/frontend/apps/tic-tac-toe/Dockerfile b/frontend/apps/tic-tac-toe/Dockerfile index 79ae5275f..6f873fccf 100644 --- a/frontend/apps/tic-tac-toe/Dockerfile +++ b/frontend/apps/tic-tac-toe/Dockerfile @@ -15,11 +15,13 @@ RUN apk update RUN apk add xsel -ARG VITE_CONTRACT_ADDRESS \ +ARG VITE_DNS_API_URL \ + VITE_DNS_NAME \ VITE_NODE_ADDRESS \ VITE_GASLESS_BACKEND_ADDRESS \ VITE_SENTRY_DSN -ENV VITE_CONTRACT_ADDRESS=${VITE_CONTRACT_ADDRESS} \ +ENV VITE_DNS_API_URL=${VITE_DNS_API_URL} \ + VITE_DNS_NAME=${VITE_DNS_NAME} \ VITE_NODE_ADDRESS=${VITE_NODE_ADDRESS} \ VITE_GASLESS_BACKEND_ADDRESS=${VITE_GASLESS_BACKEND_ADDRESS} \ VITE_SENTRY_DSN=${VITE_SENTRY_DSN} \ diff --git a/frontend/apps/tic-tac-toe/src/app/consts.ts b/frontend/apps/tic-tac-toe/src/app/consts.ts index fb8602777..36284a109 100644 --- a/frontend/apps/tic-tac-toe/src/app/consts.ts +++ b/frontend/apps/tic-tac-toe/src/app/consts.ts @@ -1,12 +1,11 @@ -import { HexString } from '@gear-js/api'; - export const ACCOUNT_ID_LOCAL_STORAGE_KEY = 'account'; export const ADDRESS = { NODE: import.meta.env.VITE_NODE_ADDRESS, - GAME: import.meta.env.VITE_CONTRACT_ADDRESS as HexString, GASLESS_BACKEND: import.meta.env.VITE_GASLESS_BACKEND_ADDRESS, BASE_NODES: import.meta.env.VITE_DEFAULT_NODES_URL, + DNS_API_URL: import.meta.env.VITE_DNS_API_URL, + DNS_NAME: import.meta.env.VITE_DNS_NAME, STAGING_NODES: import.meta.env.VITE_STAGING_NODES_URL, SENTRY_DSN: import.meta.env.VITE_SENTRY_DSN_TTT, }; diff --git a/frontend/apps/tic-tac-toe/src/app/hocs/index.tsx b/frontend/apps/tic-tac-toe/src/app/hocs/index.tsx index 930f5e84e..a98654474 100644 --- a/frontend/apps/tic-tac-toe/src/app/hocs/index.tsx +++ b/frontend/apps/tic-tac-toe/src/app/hocs/index.tsx @@ -7,6 +7,7 @@ import { import { ComponentType } from 'react'; import { BrowserRouter } from 'react-router-dom'; +import { DnsProvider as SharedDnsProvider, useDnsProgramIds } from '@dapps-frontend/hooks'; import { SignlessTransactionsProvider as SharedSignlessTransactionsProvider, GaslessTransactionsProvider as SharedGaslessTransactionsProvider, @@ -29,20 +30,27 @@ function AlertProvider({ children }: ProviderProps) { ); } +function DnsProvider({ children }: ProviderProps) { + return ( + + {children} + + ); +} + function GaslessTransactionsProvider({ children }: ProviderProps) { + const { programId } = useDnsProgramIds(); return ( - + {children} ); } function SignlessTransactionsProvider({ children }: ProviderProps) { + const { programId } = useDnsProgramIds(); return ( - + {children} ); @@ -53,6 +61,7 @@ const providers = [ ApiProvider, AccountProvider, AlertProvider, + DnsProvider, GaslessTransactionsProvider, SignlessTransactionsProvider, EzTransactionsProvider, diff --git a/frontend/apps/tic-tac-toe/src/app/hooks/use-watch-messages.ts b/frontend/apps/tic-tac-toe/src/app/hooks/use-watch-messages.ts index 527807054..57da0e0b9 100644 --- a/frontend/apps/tic-tac-toe/src/app/hooks/use-watch-messages.ts +++ b/frontend/apps/tic-tac-toe/src/app/hooks/use-watch-messages.ts @@ -3,13 +3,12 @@ import { MutableRefObject, useRef, useState } from 'react'; import { UnsubscribePromise } from '@polkadot/api/types'; import { Bytes } from '@polkadot/types'; import { ProgramMetadata, UserMessageSent, decodeAddress } from '@gear-js/api'; -import { ADDRESS } from '@/features/tic-tac-toe/consts'; import { ContractError } from '../types'; import { useSignlessTransactions } from '@dapps-frontend/ez-transactions'; - -const programId = ADDRESS.GAME; +import { useDnsProgramIds } from '@dapps-frontend/hooks'; export function useWatchMessages(meta: ProgramMetadata) { + const { programId } = useDnsProgramIds(); const { api } = useApi(); const { account } = useAccount(); const alert = useAlert(); diff --git a/frontend/apps/tic-tac-toe/src/env.d.ts b/frontend/apps/tic-tac-toe/src/env.d.ts index c6f732a48..e3cc1c7d5 100644 --- a/frontend/apps/tic-tac-toe/src/env.d.ts +++ b/frontend/apps/tic-tac-toe/src/env.d.ts @@ -1,7 +1,8 @@ interface ImportMetaEnv { readonly VITE_NODE_ADDRESS: string; readonly VITE_AUTH_API_ADDRESS: string; - readonly VITE_CONTRACT_ADDRESS: string; + readonly VITE_DNS_API_URL: string; + readonly VITE_DNS_NAME: string; readonly VITE_DEFAULT_NODES_URL: string; readonly VITE_STAGING_NODES_URL: string; readonly VITE_GTM_ID_TTT: string; diff --git a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-field/game-field.tsx b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-field/game-field.tsx index eed908408..b226b68a9 100644 --- a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-field/game-field.tsx +++ b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-field/game-field.tsx @@ -12,8 +12,7 @@ import { useEffect } from 'react'; import { useAtom } from 'jotai'; import { stateChangeLoadingAtom } from '../../store'; import { useAccount, useAlert, useHandleCalculateGas } from '@gear-js/react-hooks'; -import { ADDRESS } from '../../consts'; -import { useCheckBalance } from '@dapps-frontend/hooks'; +import { useCheckBalance, useDnsProgramIds } from '@dapps-frontend/hooks'; import { useEzTransactions } from '@dapps-frontend/ez-transactions'; import { withoutCommas } from '@/app/utils'; import { ProgramMetadata } from '@gear-js/api'; @@ -24,13 +23,14 @@ type GameFieldProps = BaseComponentProps & { }; export function GameField({ game, meta }: GameFieldProps) { + const { programId } = useDnsProgramIds(); const { signless, gasless } = useEzTransactions(); const { countdown } = useGame(); const [isLoading, setIsLoading] = useAtom(stateChangeLoadingAtom); const board = game.board; const { account } = useAccount(); const alert = useAlert(); - const calculateGas = useHandleCalculateGas(ADDRESS.GAME, meta); + const calculateGas = useHandleCalculateGas(programId, meta); const message = useGameMessage(meta); const { checkBalance } = useCheckBalance({ signlessPairVoucherId: signless.voucher?.id, @@ -46,7 +46,7 @@ export function GameField({ game, meta }: GameFieldProps) { }; const onSelectCell = async (value: number) => { - if (!meta || !account || !ADDRESS.GAME) { + if (!meta || !account || !programId) { return; } diff --git a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-skip-button/game-skip-button.tsx b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-skip-button/game-skip-button.tsx index a240ddee5..e19f9359f 100644 --- a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-skip-button/game-skip-button.tsx +++ b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-skip-button/game-skip-button.tsx @@ -2,18 +2,18 @@ import { Button } from '@/components/ui/button'; import { useGameMessage, useSubscriptionOnGameMessage } from '../../hooks'; import { useEffect, useState } from 'react'; import { useAccount, useAlert, useHandleCalculateGas } from '@gear-js/react-hooks'; -import { ADDRESS } from '../../consts'; import { withoutCommas } from '@/app/utils'; import { ProgramMetadata } from '@gear-js/api'; import { useEzTransactions } from '@dapps-frontend/ez-transactions'; -import { useCheckBalance } from '@dapps-frontend/hooks'; +import { useCheckBalance, useDnsProgramIds } from '@dapps-frontend/hooks'; type Props = { meta: ProgramMetadata; }; export function GameSkipButton({ meta }: Props) { - const calculateGas = useHandleCalculateGas(ADDRESS.GAME, meta); + const { programId } = useDnsProgramIds(); + const calculateGas = useHandleCalculateGas(programId, meta); const message = useGameMessage(meta); const alert = useAlert(); const { account } = useAccount(); @@ -42,7 +42,7 @@ export function GameSkipButton({ meta }: Props) { }; const onNextTurn = async () => { - if (!meta || !account || !ADDRESS.GAME) { + if (!meta || !account || !programId) { return; } diff --git a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-start-button/game-start-button.tsx b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-start-button/game-start-button.tsx index 541a28ed8..6691377c2 100644 --- a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-start-button/game-start-button.tsx +++ b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/components/game-start-button/game-start-button.tsx @@ -2,9 +2,8 @@ import { Button } from '@/components/ui/button'; import { useGameMessage, useSubscriptionOnGameMessage } from '../../hooks'; import { useEffect } from 'react'; import { BaseComponentProps } from '@/app/types'; -import { useCheckBalance } from '@dapps-frontend/hooks'; +import { useCheckBalance, useDnsProgramIds } from '@dapps-frontend/hooks'; import { useAccount, useAlert, useHandleCalculateGas } from '@gear-js/react-hooks'; -import { ADDRESS } from '../../consts'; import { withoutCommas } from '@/app/utils'; import { ProgramMetadata } from '@gear-js/api'; import { useGaslessTransactions, useSignlessTransactions } from '@dapps-frontend/ez-transactions'; @@ -16,13 +15,14 @@ type GameStartButtonProps = BaseComponentProps & { }; export function GameStartButton({ children, meta }: GameStartButtonProps) { + const { programId } = useDnsProgramIds(); const message = useGameMessage(meta); const { account } = useAccount(); const alert = useAlert(); const signless = useSignlessTransactions(); const gasless = useGaslessTransactions(); - const calculateGas = useHandleCalculateGas(ADDRESS.GAME, meta); + const calculateGas = useHandleCalculateGas(programId, meta); const { checkBalance } = useCheckBalance({ signlessPairVoucherId: signless.voucher?.id, @@ -43,7 +43,7 @@ export function GameStartButton({ children, meta }: GameStartButtonProps) { }; const onGameStart = async () => { - if (!meta || !account || !ADDRESS.GAME) { + if (!meta || !account || !programId) { return; } const payload = { StartGame: {} }; @@ -77,7 +77,7 @@ export function GameStartButton({ children, meta }: GameStartButtonProps) { }; return ( - ); diff --git a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/consts.ts b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/consts.ts deleted file mode 100644 index 0a7ef3a8e..000000000 --- a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/consts.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { HexString } from '@polkadot/util/types'; - -export const ADDRESS = { - GAME: import.meta.env.VITE_CONTRACT_ADDRESS as HexString, -}; diff --git a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/hooks.ts b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/hooks.ts index e9bd9a538..85c065089 100644 --- a/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/hooks.ts +++ b/frontend/apps/tic-tac-toe/src/features/tic-tac-toe/hooks.ts @@ -1,22 +1,16 @@ -import { useAccount, useApi, useBalance } from '@gear-js/react-hooks'; -import { useEffect, useMemo, useState } from 'react'; +import { useAccount, useApi } from '@gear-js/react-hooks'; +import { useEffect, useMemo } from 'react'; import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import isEqual from 'lodash.isequal'; -import { - useGaslessTransactions, - useSignlessSendMessage, - useSignlessTransactions, -} from '@dapps-frontend/ez-transactions'; +import { useDnsProgramIds } from '@dapps-frontend/hooks'; +import { useSignlessSendMessage } from '@dapps-frontend/ez-transactions'; import { IDecodedReplyGame, IGameInstance, IQueryResponseConfig, IQueryResponseGame } from './types'; import { configAtom, countdownAtom, gameAtom, pendingAtom, stateChangeLoadingAtom } from './store'; -import { ADDRESS } from './consts'; import { useOnceReadState } from '@/app/hooks/use-once-read-state'; import { useWatchMessages } from '@/app/hooks/use-watch-messages'; import { toNumber } from '@/app/utils'; import { ProgramMetadata } from '@gear-js/api'; -const programIdGame = ADDRESS.GAME; - export function useGame() { const setGameState = useSetAtom(gameAtom); const gameState = useAtomValue(gameAtom); @@ -66,6 +60,7 @@ export function useGame() { export function useOnceGameState(metadata?: ProgramMetadata) { const { account } = useAccount(); + const { programId } = useDnsProgramIds(); const payloadGame = useMemo( () => (account?.decodedAddress ? { Game: { player_id: account.decodedAddress } } : undefined), @@ -78,7 +73,7 @@ export function useOnceGameState(metadata?: ProgramMetadata) { error: configError, handleReadState: triggerConfig, } = useOnceReadState({ - programId: programIdGame, + programId, payload: payloadConfig, meta: metadata, }); @@ -88,7 +83,7 @@ export function useOnceGameState(metadata?: ProgramMetadata) { error: gameError, handleReadState: triggerGame, } = useOnceReadState({ - programId: programIdGame, + programId, payload: payloadGame, meta: metadata, }); @@ -154,7 +149,8 @@ export const useInitGameSync = (metadata?: ProgramMetadata) => { }; export function useGameMessage(meta: ProgramMetadata) { - return useSignlessSendMessage(ADDRESS.GAME, meta, { disableAlerts: true }); + const { programId } = useDnsProgramIds(); + return useSignlessSendMessage(programId, meta, { disableAlerts: true }); } export function usePending() { diff --git a/frontend/packages/hooks/src/hooks/index.ts b/frontend/packages/hooks/src/hooks/index.ts index b7c101107..05d408597 100644 --- a/frontend/packages/hooks/src/hooks/index.ts +++ b/frontend/packages/hooks/src/hooks/index.ts @@ -2,5 +2,6 @@ import { useCountdown } from './use-countdown'; import { useProgramMetadata } from './use-program-metadata'; import { useHandleCalculateGas } from './use-calculate-gas'; import { useCheckBalance } from './use-check-balance'; +import { useDnsProgramIds } from './use-dns-program-id'; -export { useCountdown, useProgramMetadata, useHandleCalculateGas, useCheckBalance }; +export { useCountdown, useProgramMetadata, useHandleCalculateGas, useCheckBalance, useDnsProgramIds }; diff --git a/frontend/packages/hooks/src/hooks/use-dns-program-id.ts b/frontend/packages/hooks/src/hooks/use-dns-program-id.ts new file mode 100644 index 000000000..033ddae8a --- /dev/null +++ b/frontend/packages/hooks/src/hooks/use-dns-program-id.ts @@ -0,0 +1,15 @@ +import { useContext } from 'react'; +import { DnsContext, DefaultDnsValueName } from '../providers/dns-provider'; +import { HexString } from '@gear-js/api'; + +function useDnsProgramIds() { + const context = useContext(DnsContext); + + if (context === undefined) { + throw new Error('useDnsProgramIds must be used within a DnsProvider'); + } + + return context as Record; +} + +export { useDnsProgramIds }; diff --git a/frontend/packages/hooks/src/index.ts b/frontend/packages/hooks/src/index.ts index 27c031a52..d69f4573a 100644 --- a/frontend/packages/hooks/src/index.ts +++ b/frontend/packages/hooks/src/index.ts @@ -1,4 +1,12 @@ -import { useCountdown, useProgramMetadata, useHandleCalculateGas, useCheckBalance } from './hooks'; -import { AvailableBalanceProvider } from './providers'; +import { useCountdown, useProgramMetadata, useHandleCalculateGas, useCheckBalance, useDnsProgramIds } from './hooks'; +import { AvailableBalanceProvider, DnsProvider } from './providers'; -export { useCountdown, useProgramMetadata, useHandleCalculateGas, useCheckBalance, AvailableBalanceProvider }; +export { + useCountdown, + useProgramMetadata, + useHandleCalculateGas, + useCheckBalance, + AvailableBalanceProvider, + DnsProvider, + useDnsProgramIds, +}; diff --git a/frontend/packages/hooks/src/providers/dns-provider.tsx b/frontend/packages/hooks/src/providers/dns-provider.tsx new file mode 100644 index 000000000..d2f14d7a1 --- /dev/null +++ b/frontend/packages/hooks/src/providers/dns-provider.tsx @@ -0,0 +1,66 @@ +import { createContext, useEffect, useState, ReactNode, PropsWithChildren } from 'react'; +import { useAlert } from '@gear-js/react-hooks'; +import { HexString } from '@gear-js/api'; + +export type DnsContextValue = Record; + +export type DefaultDnsValueName = 'programId'; + +export type DnsProviderProps = { + names: Record; + dnsApiUrl: string; + fallback?: ReactNode; +}; + +export type DnsResponse = { + id: string; + name: string; + address: HexString; + createdBy: HexString; + createdAt: string; + updatedAt: string; +}; + +const DnsContext = createContext({}); + +function DnsProvider({ + children, + names, + dnsApiUrl, + fallback, +}: PropsWithChildren>) { + const [programIds, setProgramIds] = useState({}); + const alert = useAlert(); + + useEffect(() => { + const init = async () => { + if (!dnsApiUrl || !names) { + throw new Error('dnsApiUrl or names is undefined'); + } + try { + const promises = Object.entries(names).map(async ([key, name]) => { + const response = await fetch(`${dnsApiUrl}/dns/by_name/${name}`); + const dns: DnsResponse = await response.json(); + return { [key]: dns.address }; + }); + + const results = await Promise.all(promises); + const addresses = results.reduce((acc, current) => ({ ...acc, ...current }), {}); + + setProgramIds(addresses); + } catch (error) { + const { message } = error as Error; + alert.error(message); + console.error(message); + } + }; + + init(); + }, [names, dnsApiUrl]); + + return ( + {Object.keys(programIds).length ? children : fallback} + ); +} + +export { DnsContext, DnsProvider }; diff --git a/frontend/packages/hooks/src/providers/index.ts b/frontend/packages/hooks/src/providers/index.ts index 798d23969..f5fb4ff51 100644 --- a/frontend/packages/hooks/src/providers/index.ts +++ b/frontend/packages/hooks/src/providers/index.ts @@ -1,3 +1,4 @@ import { BalanceProvider as AvailableBalanceProvider } from './balance-provider'; +import { DnsProvider } from './dns-provider'; -export { AvailableBalanceProvider }; +export { AvailableBalanceProvider, DnsProvider };