diff --git a/ui/src/app/api/getGoldenTokens.ts b/ui/src/app/api/getGoldenTokens.ts new file mode 100644 index 000000000..537d6bd7c --- /dev/null +++ b/ui/src/app/api/getGoldenTokens.ts @@ -0,0 +1,53 @@ +import { Network } from "@/app/hooks/useUIStore"; +import { networkConfig } from "@/app/lib/networkConfig"; +import { indexAddress } from "@/app/lib/utils"; + +export const getGoldenTokens = async ( + owner: string, + goldenTokenAddress: string, + network: Network +): Promise => { + const recursiveFetch: any = async ( + goldenTokens: any[], + nextPageKey: string | null + ) => { + let url = `${ + networkConfig[network!].blastUrl + }/builder/getWalletNFTs?contractAddress=${goldenTokenAddress}&walletAddress=${indexAddress( + owner + ).toLowerCase()}&pageSize=100`; + + if (nextPageKey) { + url += `&pageKey=${nextPageKey}`; + } + + try { + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + const data = await response.json(); + goldenTokens = goldenTokens.concat( + data?.nfts?.map((goldenToken: any) => { + const tokenId = JSON.parse(goldenToken.tokenId); + return Number(tokenId); + }) + ); + + if (data.nextPageKey) { + return recursiveFetch(goldenTokens, data.nextPageKey); + } + } catch (ex) { + console.log("error fetching golden tokens", ex); + } + + return goldenTokens; + }; + + let goldenTokenData = await recursiveFetch([], null); + + return goldenTokenData; +}; diff --git a/ui/src/app/components/start/CreateAdventurer.tsx b/ui/src/app/components/start/CreateAdventurer.tsx index b72e30b19..7a5397a83 100644 --- a/ui/src/app/components/start/CreateAdventurer.tsx +++ b/ui/src/app/components/start/CreateAdventurer.tsx @@ -9,12 +9,10 @@ export interface CreateAdventurerProps { isActive: boolean; onEscape: () => void; spawn: (...args: any[]) => any; - lordsBalance?: bigint; - goldenTokenData: any; + goldenTokens: number[]; blobertsData: any; gameContract: Contract; getBalances: () => Promise; - mintLords: (lordsAmount: number) => Promise; costToPlay: bigint; } @@ -22,12 +20,10 @@ export const CreateAdventurer = ({ isActive, onEscape, spawn, - lordsBalance, - goldenTokenData, + goldenTokens, blobertsData, gameContract, getBalances, - mintLords, costToPlay, }: CreateAdventurerProps) => { const [formData, setFormData] = useState({ @@ -119,12 +115,10 @@ export const CreateAdventurer = ({ formData={formData} spawn={spawn} handleBack={handleBack} - lordsBalance={lordsBalance} - goldenTokenData={goldenTokenData} + goldenTokens={goldenTokens} blobertsData={blobertsData} gameContract={gameContract} getBalances={getBalances} - mintLords={mintLords} costToPlay={costToPlay} /> @@ -145,12 +139,10 @@ export const CreateAdventurer = ({ formData={formData} spawn={spawn} handleBack={handleBack} - lordsBalance={lordsBalance} - goldenTokenData={goldenTokenData} + goldenTokens={goldenTokens} blobertsData={blobertsData} gameContract={gameContract} getBalances={getBalances} - mintLords={mintLords} costToPlay={costToPlay} /> diff --git a/ui/src/app/components/start/Spawn.tsx b/ui/src/app/components/start/Spawn.tsx index 13e0f79af..6a234fd7f 100644 --- a/ui/src/app/components/start/Spawn.tsx +++ b/ui/src/app/components/start/Spawn.tsx @@ -13,7 +13,7 @@ import useUIStore from "@/app/hooks/useUIStore"; import { battle } from "@/app/lib/constants"; import { networkConfig } from "@/app/lib/networkConfig"; import { formatLords } from "@/app/lib/utils"; -import { Adventurer, FormData, GameToken } from "@/app/types"; +import { Adventurer, FormData } from "@/app/types"; import Image from "next/image"; import Lords from "public/icons/lords.svg"; import { useEffect, useMemo, useState } from "react"; @@ -30,12 +30,10 @@ export interface SpawnProps { costToPlay?: number ) => Promise; handleBack: () => void; - lordsBalance?: bigint; - goldenTokenData: any; + goldenTokens: number[]; blobertsData: any; gameContract: Contract; getBalances: () => Promise; - mintLords: (lordsAmount: number) => Promise; costToPlay: bigint; } @@ -43,12 +41,10 @@ export const Spawn = ({ formData, spawn, handleBack, - lordsBalance, - goldenTokenData, + goldenTokens, blobertsData, gameContract, getBalances, - mintLords, costToPlay, }: SpawnProps) => { const [paymentInitiated, setPaymentInitiated] = useState(false); @@ -89,11 +85,6 @@ export const Spawn = ({ } }; - const goldenTokens = goldenTokenData?.getERC721Tokens; - const goldenTokenIds: number[] = goldenTokens?.map( - (token: GameToken) => token.token_id - ); - const getUsableGoldenToken = async (tokenIds: number[]) => { // Loop through contract calls to see if the token is usable, if none then return 0 for (let tokenId of tokenIds) { @@ -133,7 +124,7 @@ export const Spawn = ({ const tournamentEnded = process.env.NEXT_PUBLIC_TOURNAMENT_ENDED === "true"; useEffect(() => { - getUsableGoldenToken(goldenTokenIds ?? []); + getUsableGoldenToken(goldenTokens ?? []); if (tournamentEnded) { getUsableBlobertToken(blobertTokenIds ?? []); } diff --git a/ui/src/app/containers/AdventurerScreen.tsx b/ui/src/app/containers/AdventurerScreen.tsx index 325d45e2b..af70e3d42 100644 --- a/ui/src/app/containers/AdventurerScreen.tsx +++ b/ui/src/app/containers/AdventurerScreen.tsx @@ -24,12 +24,10 @@ interface AdventurerScreenProps { costToPlay?: number ) => Promise; handleSwitchAdventurer: (adventurerId: number) => Promise; - lordsBalance?: bigint; gameContract: Contract; - goldenTokenData: any; + goldenTokens: number[]; blobertsData: any; getBalances: () => Promise; - mintLords: (lordsAmount: number) => Promise; costToPlay: bigint; transferAdventurer: ( account: AccountInterface, @@ -46,12 +44,10 @@ interface AdventurerScreenProps { export default function AdventurerScreen({ spawn, handleSwitchAdventurer, - lordsBalance, gameContract, - goldenTokenData, + goldenTokens, blobertsData, getBalances, - mintLords, costToPlay, transferAdventurer, }: AdventurerScreenProps) { @@ -137,12 +133,10 @@ export default function AdventurerScreen({ isActive={activeMenu == 1} onEscape={() => setActiveMenu(0)} spawn={spawn} - lordsBalance={lordsBalance} - goldenTokenData={goldenTokenData} + goldenTokens={goldenTokens} blobertsData={blobertsData} gameContract={gameContract} getBalances={getBalances} - mintLords={mintLords} costToPlay={costToPlay} /> diff --git a/ui/src/app/hooks/useCustomQuery.ts b/ui/src/app/hooks/useCustomQuery.ts index e25c4f7b5..18b29be5f 100644 --- a/ui/src/app/hooks/useCustomQuery.ts +++ b/ui/src/app/hooks/useCustomQuery.ts @@ -1,9 +1,7 @@ -import { useEffect, useCallback, useMemo } from "react"; -import { useQuery } from "@apollo/client"; -import { useQueriesStore, QueryKey } from "@/app/hooks/useQueryStore"; -import { gameClient } from "@/app/lib/clients"; +import { QueryKey, useQueriesStore } from "@/app/hooks/useQueryStore"; import { Network } from "@/app/hooks/useUIStore"; -import { networkConfig } from "@/app/lib/networkConfig"; +import { useQuery } from "@apollo/client"; +import { useCallback, useEffect } from "react"; type Variables = Record< string, @@ -21,13 +19,7 @@ const useCustomQuery = ( setRefetch: state.setRefetch, })); - // Memoize the Apollo Client instance based on clientType - const client = useMemo(() => { - return gameClient(networkConfig[network!].lsGQLURL); - }, [network]); - const { data, refetch } = useQuery(query, { - client: client, variables: variables, skip: skip, }); diff --git a/ui/src/app/layout.tsx b/ui/src/app/layout.tsx index f7c0417a9..c903d3ea3 100644 --- a/ui/src/app/layout.tsx +++ b/ui/src/app/layout.tsx @@ -1,21 +1,21 @@ "use client"; -import { useEffect, useState } from "react"; -import { ApolloProvider } from "@apollo/client"; +import BurnerLoader from "@/app/components/animations/BurnerLoader"; +import Intro from "@/app/components/intro/Intro"; +import LoginIntro from "@/app/components/onboarding/Intro"; import { ControllerProvider } from "@/app/context/ControllerContext"; -import { gameClient, goldenTokenClient } from "@/app/lib/clients"; -import useUIStore from "@/app/hooks/useUIStore"; -import { StarknetProvider } from "@/app/provider"; import { DojoProvider } from "@/app/dojo/DojoContext"; import { setup } from "@/app/dojo/setup"; -import LoginIntro from "@/app/components/onboarding/Intro"; -import Intro from "@/app/components/intro/Intro"; import "@/app/globals.css"; -import { BurnerManager } from "@dojoengine/create-burner"; -import { RpcProvider } from "starknet"; import Head from "@/app/head"; +import useUIStore from "@/app/hooks/useUIStore"; +import { gameClient } from "@/app/lib/clients"; +import { StarknetProvider } from "@/app/provider"; +import { ApolloProvider } from "@apollo/client"; +import { BurnerManager } from "@dojoengine/create-burner"; import { Analytics } from "@vercel/analytics/react"; -import BurnerLoader from "@/app/components/animations/BurnerLoader"; +import { useEffect, useState } from "react"; +import { RpcProvider } from "starknet"; import { networkConfig } from "./lib/networkConfig"; type SetupResult = { @@ -83,15 +83,11 @@ export default function RootLayout({ ) : ( - - - - {children} - - - + + + {children} + + )} diff --git a/ui/src/app/lib/networkConfig.ts b/ui/src/app/lib/networkConfig.ts index ce0d100dd..e968be0a2 100644 --- a/ui/src/app/lib/networkConfig.ts +++ b/ui/src/app/lib/networkConfig.ts @@ -3,7 +3,8 @@ export const networkConfig = { rpcUrl: "https://starknet-sepolia.blastapi.io/5ef61753-e7c1-4593-bc62-97fdf96f8de5", lsGQLURL: "https://ls-indexer-sepolia.provable.games/graphql", - tokensGQLURL: "https://testnet.realms.world/api/graphql", + blastUrl: + "https://starknet-mainnet.blastapi.io/5ef61753-e7c1-4593-bc62-97fdf96f8de5", ethAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", gameAddress: @@ -35,7 +36,8 @@ export const networkConfig = { mainnet: { rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet", lsGQLURL: "https://ls-indexer-sepolia.provable.games/graphql", - tokensGQLURL: "https://realms.world/api/graphql", + blastUrl: + "https://starknet-mainnet.blastapi.io/5ef61753-e7c1-4593-bc62-97fdf96f8de5", ethAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", gameAddress: @@ -73,7 +75,7 @@ export const networkConfig = { katana: { rpcUrl: "https://ls-katana.provable.games:5443/", lsGQLURL: "https://ls-katana.provable.games:8080/graphql", - tokensGQLURL: "", + blastUrl: "", ethAddress: "0x0", gameAddress: "0x075cecf3e76521c1b925a640b3f802294e67db09072d9b98090aff55a68e0e3f", @@ -102,7 +104,7 @@ export const networkConfig = { rpcUrl: "http://localhost:5050", rpcAPIKey: "", lsGQLURL: "http://localhost:8080/graphql", - tokensGQLURL: "", + blastUrl: "", ethAddress: "0x0", gameAddress: "0x0047339334c614ce3dd6ce0321dfc81c87a1c523fc10b755f162ac7bc4b60342", diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index 2de3cb5a3..55e34ac8e 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -4,8 +4,10 @@ import EthBalanceFragment from "@/app/abi/EthBalanceFragment.json"; import Game from "@/app/abi/Game.json"; import Lords from "@/app/abi/Lords.json"; import Pragma from "@/app/abi/Pragma.json"; +import { getGoldenTokens } from "@/app/api/getGoldenTokens"; import { DeathDialog } from "@/app/components/adventurer/DeathDialog"; import Player from "@/app/components/adventurer/Player"; +import { StatRemovalWarning } from "@/app/components/adventurer/StatRemovalWarning"; import TokenLoader from "@/app/components/animations/TokenLoader"; import EncounterDialog from "@/app/components/encounters/EnounterDialog"; import WalletSelect from "@/app/components/intro/WalletSelect"; @@ -20,11 +22,13 @@ import { SpecialBeast } from "@/app/components/notifications/SpecialBeast"; import { ProfileDialog } from "@/app/components/profile/ProfileDialog"; import ActionsScreen from "@/app/containers/ActionsScreen"; import AdventurerScreen from "@/app/containers/AdventurerScreen"; +import CollectionsLeaderboardScreen from "@/app/containers/CollectionsLeaderboardScreen"; import EncountersScreen from "@/app/containers/EncountersScreen"; import GuideScreen from "@/app/containers/GuideScreen"; import InterludeScreen from "@/app/containers/InterludeScreen"; import InventoryScreen from "@/app/containers/InventoryScreen"; import LeaderboardScreen from "@/app/containers/LeaderboardScreen"; +import Onboarding from "@/app/containers/Onboarding"; import Profile from "@/app/containers/ProfileScreen"; import TopUp from "@/app/containers/TopUp"; import UpgradeScreen from "@/app/containers/UpgradeScreen"; @@ -34,7 +38,6 @@ import { getAdventurersByOwner, getBattlesByBeast, getBeast, - getGoldenTokensByOwner, getItemsByAdventurer, getLastBeastDiscovery, getLatestDiscoveries, @@ -52,7 +55,6 @@ import useTransactionCartStore from "@/app/hooks/useTransactionCartStore"; import useTransactionManager from "@/app/hooks/useTransactionManager"; import useUIStore, { ScreenPage } from "@/app/hooks/useUIStore"; import { fetchBalances, fetchEthBalance } from "@/app/lib/balances"; -import { gameClient, goldenTokenClient } from "@/app/lib/clients"; import { VRF_WAIT_TIME } from "@/app/lib/constants"; import { networkConfig } from "@/app/lib/networkConfig"; import { @@ -69,9 +71,6 @@ import { sepolia } from "@starknet-react/chains"; import { useConnect, useContract, useProvider } from "@starknet-react/core"; import { useCallback, useEffect, useMemo, useState } from "react"; import { constants } from "starknet"; -import { StatRemovalWarning } from "./components/adventurer/StatRemovalWarning"; -import CollectionsLeaderboardScreen from "./containers/CollectionsLeaderboardScreen"; -import Onboarding from "./containers/Onboarding"; export default function Main() { return ( @@ -95,6 +94,7 @@ function Home() { const updateAdventurerStats = useAdventurerStore( (state) => state.updateAdventurerStats ); + const [goldenTokens, setGoldenTokens] = useState([]); const calls = useTransactionCartStore((state) => state.calls); const screen = useUIStore((state) => state.screen); const setScreen = useUIStore((state) => state.setScreen); @@ -467,27 +467,18 @@ function Home() { beastVariables ); - const goldenTokenVariables = useMemo(() => { - return { - contractAddress: networkConfig[network!].goldenTokenAddress.toLowerCase(), - owner: padAddress(address ?? ""), - }; - }, [address]); - - const gameClientInstance = useMemo( - () => gameClient(networkConfig[network!].lsGQLURL), - [network] - ); - - const goldenTokenClientInstance = useMemo( - () => goldenTokenClient(networkConfig[network!].tokensGQLURL), - [network] - ); + const handleFetchGoldenTokens = async () => { + const goldenTokens = await getGoldenTokens( + address ?? "", + networkConfig[network!].goldenTokenAddress, + network + ); + setGoldenTokens(goldenTokens); + }; - const { data: goldenTokenData } = useQuery(getGoldenTokensByOwner, { - client: goldenTokenClientInstance, - variables: goldenTokenVariables, - }); + useEffect(() => { + handleFetchGoldenTokens(); + }, [address, network]); const blobertTokenVariables = useMemo(() => { return { @@ -499,7 +490,6 @@ function Home() { }, [address, network]); const { data: blobertsData } = useQuery(getOwnerTokens, { - client: gameClientInstance, variables: blobertTokenVariables, }); @@ -888,12 +878,10 @@ function Home() {