diff --git a/ui/.env b/ui/.env index 51abe46d9..1060dd74f 100644 --- a/ui/.env +++ b/ui/.env @@ -1 +1,2 @@ -NEXT_PUBLIC_NETWORK="mainnet" \ No newline at end of file +NEXT_PUBLIC_NETWORK="mainnet" +NEXT_PUBLIC_TOURNAMENT_ENDED="false" \ No newline at end of file diff --git a/ui/public/blobert.png b/ui/public/blobert.png new file mode 100644 index 000000000..13b9d7e88 Binary files /dev/null and b/ui/public/blobert.png differ diff --git a/ui/public/insert-blobert-hover.png b/ui/public/insert-blobert-hover.png new file mode 100644 index 000000000..c803a2067 Binary files /dev/null and b/ui/public/insert-blobert-hover.png differ diff --git a/ui/public/insert-blobert.png b/ui/public/insert-blobert.png new file mode 100644 index 000000000..89e30b0ce Binary files /dev/null and b/ui/public/insert-blobert.png differ diff --git a/ui/src/app/components/notifications/SpecialBeast.tsx b/ui/src/app/components/notifications/SpecialBeast.tsx index 7c2eef350..ae5c5d5b9 100644 --- a/ui/src/app/components/notifications/SpecialBeast.tsx +++ b/ui/src/app/components/notifications/SpecialBeast.tsx @@ -1,13 +1,13 @@ -import { useState, useEffect } from "react"; import { fetchBeastImage } from "@/app/api/fetchMetadata"; -import Image from "next/image"; -import useUIStore from "@/app/hooks/useUIStore"; -import { Contract } from "starknet"; -import { processBeastName } from "@/app/lib/utils"; +import { Button } from "@/app/components/buttons/Button"; import TwitterShareButton from "@/app/components/buttons/TwitterShareButtons"; import useAdventurerStore from "@/app/hooks/useAdventurerStore"; -import { Button } from "@/app/components/buttons/Button"; +import useUIStore from "@/app/hooks/useUIStore"; import { networkConfig } from "@/app/lib/networkConfig"; +import { processBeastName } from "@/app/lib/utils"; +import Image from "next/image"; +import { useEffect, useState } from "react"; +import { Contract } from "starknet"; interface SpecialBeastProps { beastsContract: Contract; @@ -52,7 +52,8 @@ export const SpecialBeast = ({ beastsContract }: SpecialBeastProps) => { const beastUrl = (networkConfig[network!].beastsViewer ?? "") + "/" + - specialBeast?.tokenId?.toString(); + specialBeast?.tokenId?.toString() + + "?beast_origin=client"; return (
diff --git a/ui/src/app/components/start/AdventurersList.tsx b/ui/src/app/components/start/AdventurersList.tsx index 4ebf0d2aa..4aeefa544 100644 --- a/ui/src/app/components/start/AdventurersList.tsx +++ b/ui/src/app/components/start/AdventurersList.tsx @@ -1,32 +1,32 @@ -import { useState, useEffect, useRef, useCallback, useMemo } from "react"; -import { - Contract, - AccountInterface, - validateAndParseAddress, - constants, -} from "starknet"; -import { StarknetIdNavigator } from "starknetid.js"; -import { useProvider } from "@starknet-react/core"; import { Button } from "@/app/components/buttons/Button"; -import useAdventurerStore from "@/app/hooks/useAdventurerStore"; import { + CartridgeIcon, + ClockIcon, CoinIcon, HeartIcon, SkullIcon, - ClockIcon, - CartridgeIcon, StarknetIdIcon, } from "@/app/components/icons/Icons"; -import useUIStore from "@/app/hooks/useUIStore"; -import { useQueriesStore } from "@/app/hooks/useQueryStore"; import LootIconLoader from "@/app/components/icons/Loader"; -import useCustomQuery from "@/app/hooks/useCustomQuery"; +import { AdventurerListCard } from "@/app/components/start/AdventurerListCard"; import { getAdventurersByOwner } from "@/app/hooks/graphql/queries"; +import useAdventurerStore from "@/app/hooks/useAdventurerStore"; +import useCustomQuery from "@/app/hooks/useCustomQuery"; import useNetworkAccount from "@/app/hooks/useNetworkAccount"; -import { indexAddress, padAddress, calculateLevel } from "@/app/lib/utils"; -import { Adventurer } from "@/app/types"; -import { AdventurerListCard } from "@/app/components/start/AdventurerListCard"; +import { useQueriesStore } from "@/app/hooks/useQueryStore"; import useTransactionCartStore from "@/app/hooks/useTransactionCartStore"; +import useUIStore from "@/app/hooks/useUIStore"; +import { calculateLevel, indexAddress, padAddress } from "@/app/lib/utils"; +import { Adventurer } from "@/app/types"; +import { useProvider } from "@starknet-react/core"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + AccountInterface, + constants, + Contract, + validateAndParseAddress, +} from "starknet"; +import { StarknetIdNavigator } from "starknetid.js"; export interface AdventurerListProps { isActive: boolean; @@ -58,7 +58,7 @@ export const AdventurersList = ({ constants.StarknetChainId.SN_MAIN ); const [selectedIndex, setSelectedIndex] = useState(-1); - const [showZeroHealth, setShowZeroHealth] = useState(true); + const [showZeroHealth, setShowZeroHealth] = useState(false); const [isTransferOpen, setIsTransferOpen] = useState(false); const [adventurerForTransfer, setAdventurerForTransfer] = useState(null); diff --git a/ui/src/app/components/start/CreateAdventurer.tsx b/ui/src/app/components/start/CreateAdventurer.tsx index f55892778..b72e30b19 100644 --- a/ui/src/app/components/start/CreateAdventurer.tsx +++ b/ui/src/app/components/start/CreateAdventurer.tsx @@ -1,9 +1,9 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { Contract } from "starknet"; -import { FormData } from "@/app/types"; import { AdventurerName } from "@/app/components/start/AdventurerName"; -import { WeaponSelect } from "@/app/components/start/WeaponSelect"; import { Spawn } from "@/app/components/start/Spawn"; +import { WeaponSelect } from "@/app/components/start/WeaponSelect"; +import { FormData } from "@/app/types"; +import React, { useCallback, useEffect, useState } from "react"; +import { Contract } from "starknet"; export interface CreateAdventurerProps { isActive: boolean; @@ -11,6 +11,7 @@ export interface CreateAdventurerProps { spawn: (...args: any[]) => any; lordsBalance?: bigint; goldenTokenData: any; + blobertsData: any; gameContract: Contract; getBalances: () => Promise; mintLords: (lordsAmount: number) => Promise; @@ -23,6 +24,7 @@ export const CreateAdventurer = ({ spawn, lordsBalance, goldenTokenData, + blobertsData, gameContract, getBalances, mintLords, @@ -119,6 +121,7 @@ export const CreateAdventurer = ({ handleBack={handleBack} lordsBalance={lordsBalance} goldenTokenData={goldenTokenData} + blobertsData={blobertsData} gameContract={gameContract} getBalances={getBalances} mintLords={mintLords} @@ -144,6 +147,7 @@ export const CreateAdventurer = ({ handleBack={handleBack} lordsBalance={lordsBalance} goldenTokenData={goldenTokenData} + blobertsData={blobertsData} gameContract={gameContract} getBalances={getBalances} mintLords={mintLords} diff --git a/ui/src/app/components/start/Spawn.tsx b/ui/src/app/components/start/Spawn.tsx index 40d442d2a..13e0f79af 100644 --- a/ui/src/app/components/start/Spawn.tsx +++ b/ui/src/app/components/start/Spawn.tsx @@ -25,12 +25,14 @@ export interface SpawnProps { spawn: ( formData: FormData, goldenTokenId: string, + blobertTokenId: string, revenueAddresses: string[], costToPlay?: number ) => Promise; handleBack: () => void; lordsBalance?: bigint; goldenTokenData: any; + blobertsData: any; gameContract: Contract; getBalances: () => Promise; mintLords: (lordsAmount: number) => Promise; @@ -43,6 +45,7 @@ export const Spawn = ({ handleBack, lordsBalance, goldenTokenData, + blobertsData, gameContract, getBalances, mintLords, @@ -50,7 +53,8 @@ export const Spawn = ({ }: SpawnProps) => { const [paymentInitiated, setPaymentInitiated] = useState(false); const [formFilled, setFormFilled] = useState(false); - const [usableToken, setUsableToken] = useState("0"); + const [usableGoldenToken, setUsableGoldenToken] = useState("0"); + const [usableBlobertToken, setUsableBlobertToken] = useState("0"); const [isHoveringLords, setIsHoveringLords] = useState(false); const [showPaymentDetails, setShowPaymentDetails] = useState(false); const isWrongNetwork = useUIStore((state) => state.isWrongNetwork); @@ -76,6 +80,7 @@ export const Spawn = ({ await spawn( formData, "0", + "0", networkConfig[network!].revenueAddresses, lordsGameCost ); @@ -84,8 +89,8 @@ export const Spawn = ({ } }; - const tokens = goldenTokenData?.getERC721Tokens; - const goldenTokens: number[] = tokens?.map( + const goldenTokens = goldenTokenData?.getERC721Tokens; + const goldenTokenIds: number[] = goldenTokens?.map( (token: GameToken) => token.token_id ); @@ -97,7 +102,26 @@ export const Spawn = ({ CallData.compile(["0", tokenId.toString()]) ); if (canPlay) { - setUsableToken(tokenId.toString()); + setUsableGoldenToken(tokenId.toString()); + break; + } + } + }; + + const blobertTokens = blobertsData?.tokens; + const blobertTokenIds: number[] = blobertTokens?.map( + (token: any) => token.tokenId + ); + + const getUsableBlobertToken = async (tokenIds: number[]) => { + // Loop through contract calls to see if the token is usable, if none then return 0 + for (let tokenId of tokenIds) { + const canPlay = await gameContract.call( + "free_game_available", + CallData.compile(["1", tokenId.toString()]) + ); + if (canPlay) { + setUsableBlobertToken(tokenId.toString()); break; } } @@ -106,11 +130,16 @@ export const Spawn = ({ const { play: spawnPlay } = useUiSounds(soundSelector.spawn); const { play: coinPlay } = useUiSounds(soundSelector.coin); + const tournamentEnded = process.env.NEXT_PUBLIC_TOURNAMENT_ENDED === "true"; + useEffect(() => { - getUsableGoldenToken(goldenTokens ?? []); + getUsableGoldenToken(goldenTokenIds ?? []); + if (tournamentEnded) { + getUsableBlobertToken(blobertTokenIds ?? []); + } }, []); - const handlePayment = async (goldenToken: boolean) => { + const handlePayment = async (goldenToken: boolean, blobertToken: boolean) => { spawnPlay(); coinPlay(); resetNotification(); @@ -118,7 +147,8 @@ export const Spawn = ({ try { await spawn( formData, - goldenToken ? usableToken : "0", + goldenToken ? usableGoldenToken : "0", + blobertToken && tournamentEnded ? usableBlobertToken : "0", networkConfig[network!].revenueAddresses, lordsGameCost ); @@ -190,11 +220,10 @@ export const Spawn = ({ onMouseEnter={() => setIsHoveringLords(true)} onMouseLeave={() => setIsHoveringLords(false)} onClick={() => { - if (usableToken !== "0") { - handlePayment(true); - } else { - handlePayment(false); - } + handlePayment( + usableGoldenToken !== "0", + usableBlobertToken !== "0" + ); }} >
@@ -206,11 +235,19 @@ export const Spawn = ({ : "bg-terminal-green/20" }`} > - {usableToken !== "0" ? ( + {usableGoldenToken !== "0" ? ( insert-lords + + ) : usableBlobertToken !== "0" ? ( + + blobert @@ -225,10 +262,14 @@ export const Spawn = ({ )} {!paymentInitiated && ( -
- +
)}
diff --git a/ui/src/app/containers/AdventurerScreen.tsx b/ui/src/app/containers/AdventurerScreen.tsx index ca39b690d..325d45e2b 100644 --- a/ui/src/app/containers/AdventurerScreen.tsx +++ b/ui/src/app/containers/AdventurerScreen.tsx @@ -19,6 +19,7 @@ interface AdventurerScreenProps { spawn: ( formData: FormData, goldenTokenId: string, + blobertTokenId: string, revenueAddresses: string[], costToPlay?: number ) => Promise; @@ -26,6 +27,7 @@ interface AdventurerScreenProps { lordsBalance?: bigint; gameContract: Contract; goldenTokenData: any; + blobertsData: any; getBalances: () => Promise; mintLords: (lordsAmount: number) => Promise; costToPlay: bigint; @@ -47,6 +49,7 @@ export default function AdventurerScreen({ lordsBalance, gameContract, goldenTokenData, + blobertsData, getBalances, mintLords, costToPlay, @@ -136,6 +139,7 @@ export default function AdventurerScreen({ spawn={spawn} lordsBalance={lordsBalance} goldenTokenData={goldenTokenData} + blobertsData={blobertsData} gameContract={gameContract} getBalances={getBalances} mintLords={mintLords} diff --git a/ui/src/app/hooks/graphql/queries.ts b/ui/src/app/hooks/graphql/queries.ts index d64bca395..886c5d125 100644 --- a/ui/src/app/hooks/graphql/queries.ts +++ b/ui/src/app/hooks/graphql/queries.ts @@ -468,26 +468,42 @@ const getCollectionsTotals = gql` } `; +const getOwnerTokens = gql` + query getOwnerTokens($token: HexValue, $owner: HexValue) { + tokens( + where: { token: { eq: $token }, nftOwnerAddress: { eq: $owner } } + limit: 1000 + ) { + hash + nftOwnerAddress + timestamp + token + tokenId + } + } +`; + export { - getLatestDiscoveries, - getLastBeastDiscovery, + getAdventurerById, + getAdventurerCounts, + getAdventurerRank, getAdventurersByOwner, getAdventurersByOwnerCount, - getAdventurerById, getAdventurersInList, - getBeast, - getKilledBeasts, + getAliveAdventurersByXPPaginated, + getAliveAdventurersCount, getBattlesByBeast, + getBeast, + getCollectionsTotals, + getDeadAdventurersByXPPaginated, getDiscoveriesAndBattlesByAdventurerPaginated, - getLatestMarketItems, + getDiscoveryBattleCount, + getGoldenTokensByOwner, getItemsByAdventurer, - getDeadAdventurersByXPPaginated, - getAliveAdventurersByXPPaginated, + getKilledBeasts, + getLastBeastDiscovery, + getLatestDiscoveries, + getLatestMarketItems, + getOwnerTokens, getScoresInList, - getGoldenTokensByOwner, - getAdventurerCounts, - getAliveAdventurersCount, - getDiscoveryBattleCount, - getAdventurerRank, - getCollectionsTotals, }; diff --git a/ui/src/app/lib/clients.ts b/ui/src/app/lib/clients.ts index 78bc632cf..ee4642755 100644 --- a/ui/src/app/lib/clients.ts +++ b/ui/src/app/lib/clients.ts @@ -1,18 +1,4 @@ import { ApolloClient, InMemoryCache } from "@apollo/client"; -// import { setContext } from "@apollo/client/link/context"; -// import { Network } from "@/app/hooks/useUIStore"; - -// const createAuthLink = () => -// setContext((_, { headers }) => { -// return { -// headers: { -// ...headers, -// "Cache-Control": "no-cache, no-store, must-revalidate", -// Pragma: "no-cache", -// Expires: "0", -// }, -// }; -// }); export const goldenTokenClient = (GQLUrl: string) => { return new ApolloClient({ @@ -36,16 +22,6 @@ export const goldenTokenClient = (GQLUrl: string) => { }; export const gameClient = (GQLUrl: string) => { - // const httpLink = createHttpLink({ - // uri: `/api/graphql-proxy?api=${network}`, - // fetchOptions: { - // next: { revalidate: 0 }, - // cache: "no-store", - // }, - // }); - - // const authLink = createAuthLink(); - return new ApolloClient({ uri: GQLUrl, defaultOptions: { diff --git a/ui/src/app/lib/networkConfig.ts b/ui/src/app/lib/networkConfig.ts index 55cfa537e..698a0d77d 100644 --- a/ui/src/app/lib/networkConfig.ts +++ b/ui/src/app/lib/networkConfig.ts @@ -14,6 +14,7 @@ export const networkConfig = { "0x041b6ffc02ce30c6e941f1b34244ef8af0b3e8a70f5528476a7a68765afd6b39", goldenTokenAddress: "0x07626660faba349aad9ad2aaa0ff8645c079fa8e043a168d640d92472806eeac", + tournamentWinnerAddress: "0x0", revenueAddresses: [ "0x0314924118945405ac0bcd6181457712795c0effc29d8dd3be86d3f3ec62adc1", ], @@ -45,6 +46,8 @@ export const networkConfig = { "0x0158160018d590d93528995b340260e65aedd76d28a686e9daa5c4e8fad0c5dd", goldenTokenAddress: "0x04f5e296c805126637552cf3930e857f380e7c078e8f00696de4fc8545356b1d", + tournamentWinnerAddress: + "0x00539f522b29ae9251dbf7443c7a950cf260372e69efab3710a11bf17a9599f1", revenueAddresses: [ "0x036cE487952f25878a0158bA4A0C2Eb5eb66f0282567163a4B893A0EA5847D2d", "0x0616E6a5F9b1f86a0Ece6E965B2f3b27E3D784be79Cb2F6304D92Db100C7D29E", @@ -77,6 +80,7 @@ export const networkConfig = { lordsAddress: "0x0", beastsAddress: "0x0", goldenTokenAddress: "0x0", + tournamentWinnerAddress: "0x0", revenueAddresses: ["0x0"], pragmaAddress: "0x0", rendererAddress: "0x0", @@ -105,6 +109,7 @@ export const networkConfig = { lordsAddress: "0x0", beastsAddress: "0x0", goldenTokenAddress: "0x0", + tournamentWinnerAddress: "0x0", revenueAddresses: ["0x0"], pragmaAddress: "0x0", rendererAddress: "0x0", diff --git a/ui/src/app/lib/utils/syscalls.ts b/ui/src/app/lib/utils/syscalls.ts index 1772e5245..e45a0d385 100644 --- a/ui/src/app/lib/utils/syscalls.ts +++ b/ui/src/app/lib/utils/syscalls.ts @@ -411,7 +411,8 @@ export function createSyscalls({ dollarPrice: bigint, freeVRF: boolean, costToPlay?: number, - goldenTokenId?: string + goldenTokenId?: string, + blobertTokenId?: string ) => [ ...(freeVRF ? [] @@ -426,7 +427,7 @@ export function createSyscalls({ ], }, ]), - ...(goldenTokenId === "0" + ...(goldenTokenId === "0" && blobertTokenId === "0" ? [ { contractAddress: lordsContract?.address ?? "", @@ -450,6 +451,7 @@ export function createSyscalls({ const spawn = async ( formData: FormData, goldenTokenId: string, + blobertTokenId: string, revenueAddresses: string[], costToPlay?: number ) => { @@ -466,7 +468,7 @@ export function createSyscalls({ goldenTokenId, "0", // delay_stat_reveal rendererContractAddress, - "0", + blobertTokenId, "0", ], }; @@ -482,7 +484,7 @@ export function createSyscalls({ if (!enoughEth && !freeVRF) { return handleInsufficientFunds("eth"); } - if (!enoughLords && goldenTokenId === "0") { + if (!enoughLords && goldenTokenId === "0" && blobertTokenId === "0") { return handleInsufficientFunds("lords"); } @@ -491,7 +493,8 @@ export function createSyscalls({ dollarPrice, freeVRF, costToPlay, - goldenTokenId + goldenTokenId, + blobertTokenId ); } diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index ef0a7ec25..2de3cb5a3 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -39,6 +39,7 @@ import { getLastBeastDiscovery, getLatestDiscoveries, getLatestMarketItems, + getOwnerTokens, } from "@/app/hooks/graphql/queries"; import useAdventurerStore from "@/app/hooks/useAdventurerStore"; import useControls from "@/app/hooks/useControls"; @@ -51,7 +52,7 @@ 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 { goldenTokenClient } from "@/app/lib/clients"; +import { gameClient, goldenTokenClient } from "@/app/lib/clients"; import { VRF_WAIT_TIME } from "@/app/lib/constants"; import { networkConfig } from "@/app/lib/networkConfig"; import { @@ -473,6 +474,11 @@ function Home() { }; }, [address]); + const gameClientInstance = useMemo( + () => gameClient(networkConfig[network!].lsGQLURL), + [network] + ); + const goldenTokenClientInstance = useMemo( () => goldenTokenClient(networkConfig[network!].tokensGQLURL), [network] @@ -483,6 +489,20 @@ function Home() { variables: goldenTokenVariables, }); + const blobertTokenVariables = useMemo(() => { + return { + token: indexAddress( + networkConfig[network!].tournamentWinnerAddress.toLowerCase() + ), + owner: indexAddress(address ?? "").toLowerCase(), + }; + }, [address, network]); + + const { data: blobertsData } = useQuery(getOwnerTokens, { + client: gameClientInstance, + variables: blobertTokenVariables, + }); + const handleSwitchAdventurer = useCallback( async (adventurerId: number) => { setIsLoading(); @@ -871,6 +891,7 @@ function Home() { lordsBalance={lordsBalance} gameContract={gameContract!} goldenTokenData={goldenTokenData} + blobertsData={blobertsData} getBalances={getBalances} mintLords={mintLords} costToPlay={costToPlay}