From 9798c65c1e4edf8528d35a0f9ef680ae0c6e70f4 Mon Sep 17 00:00:00 2001 From: Bob <80072466+bob0005@users.noreply.github.com> Date: Thu, 26 Dec 2024 23:12:13 +0100 Subject: [PATCH 1/4] Merge into main (#2603) * Fix missing arrival when sending lords & donkeys (#2594) * contracts: fix share points check * contracts: fix share points check * Add resource filter for contributions (#2596) * Add resource filter for contributions * Change % for specific resource contribution * Fix missing donkeys in bridge step 2 (#2598) * Fix missing donkeys in bridge step 2 * fix rerenders * Remove filter * Fix registration delay (#2602) * Fix registration delay * styling * Open popup on load * Add burned donkeys back (#2601) --------- Co-authored-by: Credence Co-authored-by: Loaf <90423308+ponderingdemocritus@users.noreply.github.com> --- client/src/ui/layouts/World.tsx | 6 +++ client/src/ui/modules/rewards/Rewards.tsx | 47 ++++++++++++++----- .../systems/hyperstructure/contracts.cairo | 13 +++-- landing/src/dojo/setup.ts | 12 +++-- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/client/src/ui/layouts/World.tsx b/client/src/ui/layouts/World.tsx index 1817fc8dd..e23211191 100644 --- a/client/src/ui/layouts/World.tsx +++ b/client/src/ui/layouts/World.tsx @@ -19,6 +19,7 @@ import { ADMIN_BANK_ENTITY_ID } from "@bibliothecadao/eternum"; import { getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { env } from "../../../env"; +import { rewards } from "../components/navigation/Config"; import { IS_MOBILE } from "../config"; import { LoadingOroborus } from "../modules/loading-oroborus"; import { LoadingScreen } from "../modules/LoadingScreen"; @@ -251,6 +252,11 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { fetch(); }, []); + const openPopup = useUIStore((state) => state.openPopup); + useEffect(() => { + openPopup(rewards); + }, []); + const battleViewContent = useMemo( () => (
diff --git a/client/src/ui/modules/rewards/Rewards.tsx b/client/src/ui/modules/rewards/Rewards.tsx index 01ab91d9b..f6c6d230d 100644 --- a/client/src/ui/modules/rewards/Rewards.tsx +++ b/client/src/ui/modules/rewards/Rewards.tsx @@ -16,7 +16,8 @@ import { shortString } from "starknet"; import { formatEther } from "viem"; import { env } from "../../../../env"; -const REGISTRATION_DELAY = 1800; // 1 week +const REGISTRATION_DELAY = 60 * 60 * 24 * 4; // 4 days +const BRIDGE_OUT_DELAY = 60 * 60 * 24 * 2; // 2 days export const Rewards = () => { const { @@ -35,6 +36,8 @@ export const Rewards = () => { const [timeRemaining, setTimeRemaining] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [registrationTimeRemaining, setRegistrationTimeRemaining] = useState(""); + const [bridgeOutTimeRemaining, setBridgeOutTimeRemaining] = useState(""); const prizePool = usePrizePool(); const togglePopup = useUIStore((state) => state.togglePopup); @@ -77,15 +80,24 @@ export const Rewards = () => { if (gameEnded) { const calculateTimeRemaining = () => { const currentTime = Math.floor(Date.now() / 1000); - const endTime = Number(gameEnded.timestamp + REGISTRATION_DELAY); - - if (currentTime >= endTime) { - setTimeRemaining("Registration Closed"); - return; + const registrationEndTime = Number(gameEnded.timestamp + REGISTRATION_DELAY); + const bridgeOutEndTime = Number(gameEnded.timestamp + BRIDGE_OUT_DELAY); + + // Calculate registration time + if (currentTime >= registrationEndTime) { + setRegistrationTimeRemaining("Registration Closed"); + } else { + const registrationDifference = registrationEndTime - currentTime; + setRegistrationTimeRemaining(formatTime(registrationDifference, undefined)); } - const difference = endTime - currentTime; - setTimeRemaining(formatTime(difference, undefined)); + // Calculate bridge out time + if (currentTime >= bridgeOutEndTime) { + setBridgeOutTimeRemaining("Bridge Out Closed"); + } else { + const bridgeOutDifference = bridgeOutEndTime - currentTime; + setBridgeOutTimeRemaining(formatTime(bridgeOutDifference, undefined)); + } }; calculateTimeRemaining(); @@ -135,10 +147,19 @@ export const Rewards = () => {
{Number(formatEther(prizePool)).toFixed(2)} $LORDS
- + + +
+ +
+
Time left to register
+
{registrationTimeRemaining}
+
+
+
-
Time left to register
-
{timeRemaining}
+
Time left to bridge out
+
{bridgeOutTimeRemaining}
@@ -184,9 +205,9 @@ export const Rewards = () => { ); }; -const Compartment = ({ children }: { children: React.ReactNode }) => { +const Compartment = ({ children, isCountdown }: { children: React.ReactNode; isCountdown?: boolean }) => { return ( -
+
{children}
); diff --git a/contracts/src/systems/hyperstructure/contracts.cairo b/contracts/src/systems/hyperstructure/contracts.cairo index a32c82168..da3c35725 100644 --- a/contracts/src/systems/hyperstructure/contracts.cairo +++ b/contracts/src/systems/hyperstructure/contracts.cairo @@ -39,6 +39,7 @@ trait IHyperstructureSystems { mod hyperstructure_systems { use achievement::store::{Store, StoreTrait}; use core::array::ArrayIndex; + use core::poseidon::poseidon_hash_span; use dojo::event::EventStorage; use dojo::model::ModelStorage; @@ -596,10 +597,16 @@ mod hyperstructure_systems { let (hyperstructure_entity_id, index) = *hyperstructure_shareholder_epochs.at(i); // ensure we don't double count points for the same hyperstructure - if points_already_added.get(hyperstructure_entity_id.into()) { - panic!("points already added for hyperstructure {}", hyperstructure_entity_id); + + let points_already_added_key: felt252 = poseidon_hash_span( + array![hyperstructure_entity_id.into(), index.into()].span() + ); + + if points_already_added.get(points_already_added_key) { + panic!("points already added for hyperstructure {}, epoch {}", hyperstructure_entity_id, index); }; - points_already_added.insert(hyperstructure_entity_id.into(), true); + + points_already_added.insert(points_already_added_key, true); let epoch: Epoch = world.read_model((hyperstructure_entity_id, index)); let next_epoch: Epoch = world.read_model((hyperstructure_entity_id, index + 1)); diff --git a/landing/src/dojo/setup.ts b/landing/src/dojo/setup.ts index d410857dd..368a77a0f 100644 --- a/landing/src/dojo/setup.ts +++ b/landing/src/dojo/setup.ts @@ -5,6 +5,7 @@ import { createClientComponents } from "./createClientComponents"; import { createSystemCalls } from "./createSystemCalls"; import { ClientConfigManager } from "./modelManager/ConfigManager"; import { setupNetwork } from "./setupNetwork"; +import { getEvents } from "@dojoengine/state"; export type SetupResult = Awaited>; export const configManager = ClientConfigManager.instance(); @@ -36,9 +37,9 @@ export async function setup({ ...config }: DojoConfig) { const filteredEvents = [ "BurnDonkey", // points - "HyperstructureCoOwnersChange", - "HyperstructureFinished", - "GameEnded", + // "HyperstructureCoOwnersChange", + // "HyperstructureFinished", + // "GameEnded", ]; const clauses: Clause[] = [ @@ -87,6 +88,7 @@ export async function setup({ ...config }: DojoConfig) { const sync = await syncEntities(network.toriiClient, filteredModels as any, [], false); + */ const eventSync = getEvents( network.toriiClient, network.contractComponents.events as any, @@ -101,7 +103,7 @@ export async function setup({ ...config }: DojoConfig) { false, false, ); -*/ + configManager.setDojo(components); return { @@ -109,6 +111,6 @@ export async function setup({ ...config }: DojoConfig) { components, systemCalls, //sync, - /*eventSync,*/ + eventSync, }; } From ed50b462788a5ae74b0d33edbe5e5482fea6348b Mon Sep 17 00:00:00 2001 From: Bob <80072466+bob0005@users.noreply.github.com> Date: Fri, 27 Dec 2024 00:08:10 +0100 Subject: [PATCH 2/4] merge into main (#2605) * Fix missing arrival when sending lords & donkeys (#2594) * contracts: fix share points check * contracts: fix share points check * Add resource filter for contributions (#2596) * Add resource filter for contributions * Change % for specific resource contribution * Fix missing donkeys in bridge step 2 (#2598) * Fix missing donkeys in bridge step 2 * fix rerenders * Remove filter * Fix registration delay (#2602) * Fix registration delay * styling * Open popup on load * Add burned donkeys back (#2601) * im (#2604) --------- Co-authored-by: Credence Co-authored-by: Loaf <90423308+ponderingdemocritus@users.noreply.github.com> --- client/src/dojo/setup.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/client/src/dojo/setup.ts b/client/src/dojo/setup.ts index 25d8fbf31..0af269aaa 100644 --- a/client/src/dojo/setup.ts +++ b/client/src/dojo/setup.ts @@ -183,7 +183,28 @@ export async function setup(config: DojoConfig & { state: AppStore }) { configManager.setDojo(components); - setLoading(LoadingStateKey.Events, true); + // setLoading(LoadingStateKey.Events, true); + + await getEvents( + network.toriiClient, + network.contractComponents.events as any, + [], + [], + 20000, + { + Keys: { + keys: [undefined], + pattern_matching: "VariableLen", + models: ["s0_eternum-GameEnded"], + }, + }, + false, + false, + ) + // .finally(() => { + // setLoading(LoadingStateKey.Events, false); + // }); + const eventSync = getEvents( network.toriiClient, network.contractComponents.events as any, @@ -195,7 +216,7 @@ export async function setup(config: DojoConfig & { state: AppStore }) { keys: [undefined], pattern_matching: "VariableLen", models: [ - "s0_eternum-GameEnded", + // "s0_eternum-GameEnded", "s0_eternum-HyperstructureFinished", "s0_eternum-BattleClaimData", "s0_eternum-BattleJoinData", From 4e4367ce616c3af38c5fdd996bb4c2b13a1bbf30 Mon Sep 17 00:00:00 2001 From: Bob <80072466+bob0005@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:24:18 +0100 Subject: [PATCH 3/4] Fix build (#2610) --- client/src/ui/layouts/World.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/src/ui/layouts/World.tsx b/client/src/ui/layouts/World.tsx index f35125175..3b5e24fef 100644 --- a/client/src/ui/layouts/World.tsx +++ b/client/src/ui/layouts/World.tsx @@ -257,11 +257,6 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { openPopup(rewards); }, []); - const openPopup = useUIStore((state) => state.openPopup); - useEffect(() => { - openPopup(rewards); - }, []); - const battleViewContent = useMemo( () => (
From 08b6361d435f16e6b7f02158d6c69531cfa50698 Mon Sep 17 00:00:00 2001 From: raschel <38816784+aymericdelab@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:21:42 +0100 Subject: [PATCH 4/4] next into main (#2615) --- client/src/dojo/contractComponents.ts | 3 +- client/src/dojo/setup.ts | 10 +- client/src/hooks/helpers/useContributions.tsx | 28 +++ .../src/hooks/helpers/useHyperstructures.tsx | 38 ++++ client/src/ui/modules/rewards/Rewards.tsx | 62 +++-- .../src/components/modules/app-sidebar.tsx | 4 +- .../modules/season-registration-timer.tsx | 45 ++++ .../modules/top-navigation-view.tsx | 2 + landing/src/dojo/createSystemCalls.ts | 15 ++ .../leaderboard/LeaderboardManager.ts | 17 +- landing/src/hooks/gql/gql.ts | 96 ++++---- landing/src/hooks/gql/graphql.ts | 129 +++++++++++ landing/src/hooks/query/leaderboard.tsx | 91 ++++++++ landing/src/hooks/usePrizeClaim.tsx | 97 ++++++++ landing/src/routeTree.gen.ts | 212 +++++++++++------- landing/src/routes/claim.lazy.tsx | 184 +++++++++++++++ landing/src/routes/index.lazy.tsx | 11 +- 17 files changed, 879 insertions(+), 165 deletions(-) create mode 100644 landing/src/components/modules/season-registration-timer.tsx create mode 100644 landing/src/hooks/query/leaderboard.tsx create mode 100644 landing/src/hooks/usePrizeClaim.tsx create mode 100644 landing/src/routes/claim.lazy.tsx diff --git a/client/src/dojo/contractComponents.ts b/client/src/dojo/contractComponents.ts index 1b9dd3d93..d173da9f4 100644 --- a/client/src/dojo/contractComponents.ts +++ b/client/src/dojo/contractComponents.ts @@ -649,13 +649,14 @@ export function defineContractComponents(world: World) { { address: RecsType.BigInt, hyperstructure_entity_id: RecsType.Number, + epoch: RecsType.Number, registered: RecsType.Boolean, }, { metadata: { namespace: "s0_eternum", name: "LeaderboardRegisterShare", - types: ["contractaddress", "u32", "bool"], + types: ["contractaddress", "u32", "u16", "bool"], customTypes: [], }, }, diff --git a/client/src/dojo/setup.ts b/client/src/dojo/setup.ts index 5e31740e2..33b3032e7 100644 --- a/client/src/dojo/setup.ts +++ b/client/src/dojo/setup.ts @@ -157,14 +157,14 @@ export async function setup(config: DojoConfig & { state: AppStore }) { Keys: { keys: [undefined, undefined], pattern_matching: "FixedLen", - models: ["s0_eternum-Epoch", "s0_eternum-Progress"], + models: ["s0_eternum-Epoch", "s0_eternum-Progress", "s0_eternum-LeaderboardRegisterContribution"], }, }, { Keys: { keys: [undefined, undefined, undefined], pattern_matching: "FixedLen", - models: ["s0_eternum-Contribution"], + models: ["s0_eternum-Contribution", "s0_eternum-LeaderboardRegisterShare"], }, }, ], @@ -199,6 +199,10 @@ export async function setup(config: DojoConfig & { state: AppStore }) { "s0_eternum-Structure", "s0_eternum-Battle", "s0_eternum-Guild", + "s0_eternum-LeaderboardRegistered", + "s0_eternum-Leaderboard", + "s0_eternum-LeaderboardRewardClaimed", + "s0_eternum-LeaderboardEntry", ], }, }, @@ -232,7 +236,7 @@ export async function setup(config: DojoConfig & { state: AppStore }) { }, false, false, - ) + ); // .finally(() => { // setLoading(LoadingStateKey.Events, false); // }); diff --git a/client/src/hooks/helpers/useContributions.tsx b/client/src/hooks/helpers/useContributions.tsx index ed3e2282f..9024c0ab9 100644 --- a/client/src/hooks/helpers/useContributions.tsx +++ b/client/src/hooks/helpers/useContributions.tsx @@ -69,3 +69,31 @@ export const useGetHyperstructuresWithContributionsFromPlayer = () => { return getContributions; }; + +export const useGetUnregisteredContributions = () => { + const { + account: { account }, + setup: { + components: { LeaderboardRegisterContribution }, + }, + } = useDojo(); + const getContributions = useGetHyperstructuresWithContributionsFromPlayer(); + + const getUnregisteredContributions = useCallback(() => { + const registeredContributionsEntities = runQuery([ + HasValue(LeaderboardRegisterContribution, { address: ContractAddress(account.address) }), + ]); + const registeredContributions = Array.from(registeredContributionsEntities) + .map((entityId) => getComponentValue(LeaderboardRegisterContribution, entityId)?.hyperstructure_entity_id) + .filter((x): x is number => x !== undefined); + console.log("registeredContributions", registeredContributions); + const hyperstructuresContributedTo = Array.from(getContributions()); + console.log("hyperstructuresContributedTo", hyperstructuresContributedTo); + return hyperstructuresContributedTo.filter( + (hyperstructureEntityId) => + !registeredContributions.some((contribution) => contribution === hyperstructureEntityId), + ); + }, [getContributions]); + + return getUnregisteredContributions; +}; diff --git a/client/src/hooks/helpers/useHyperstructures.tsx b/client/src/hooks/helpers/useHyperstructures.tsx index 6dc141526..955d2d105 100644 --- a/client/src/hooks/helpers/useHyperstructures.tsx +++ b/client/src/hooks/helpers/useHyperstructures.tsx @@ -172,6 +172,44 @@ export const useGetPlayerEpochs = () => { return getEpochs; }; +export const useGetUnregisteredEpochs = () => { + const { + account: { account }, + setup: { + components: { LeaderboardRegisterShare }, + }, + } = useDojo(); + + const getEpochs = useGetPlayerEpochs(); + + const getUnregisteredShares = useCallback(() => { + const epochs = getEpochs(); + console.log("epochs", epochs); + + const registeredSharesEntities = runQuery([ + Has(LeaderboardRegisterShare), + HasValue(LeaderboardRegisterShare, { address: ContractAddress(account.address) }), + ]); + const registeredShares = Array.from(registeredSharesEntities) + .map((shareEntityId) => { + return getComponentValue(LeaderboardRegisterShare, shareEntityId); + }) + .filter( + (share): share is ComponentValue => share !== undefined, + ); + console.log("registeredShares", registeredShares); + + return epochs.filter( + (epoch) => + !registeredShares.some( + (share) => share.epoch === epoch.epoch && share.hyperstructure_entity_id === epoch.hyperstructure_entity_id, + ), + ); + }, [getEpochs]); + + return getUnregisteredShares; +}; + const getContributions = (hyperstructureEntityId: ID, Contribution: Component) => { const contributions = runQuery([ Has(Contribution), diff --git a/client/src/ui/modules/rewards/Rewards.tsx b/client/src/ui/modules/rewards/Rewards.tsx index 435880949..697b310f4 100644 --- a/client/src/ui/modules/rewards/Rewards.tsx +++ b/client/src/ui/modules/rewards/Rewards.tsx @@ -1,14 +1,17 @@ import { useDojo } from "@/hooks/context/DojoContext"; import { usePrizePool } from "@/hooks/helpers/use-rewards"; -import { useGetHyperstructuresWithContributionsFromPlayer } from "@/hooks/helpers/useContributions"; -import { useGetPlayerEpochs } from "@/hooks/helpers/useHyperstructures"; +import { + useGetHyperstructuresWithContributionsFromPlayer, + useGetUnregisteredContributions, +} from "@/hooks/helpers/useContributions"; +import { useGetPlayerEpochs, useGetUnregisteredEpochs } from "@/hooks/helpers/useHyperstructures"; import useUIStore from "@/hooks/store/useUIStore"; import { HintSection } from "@/ui/components/hints/HintModal"; import { rewards } from "@/ui/components/navigation/Config"; import { OSWindow } from "@/ui/components/navigation/OSWindow"; import Button from "@/ui/elements/Button"; import { formatTime, getEntityIdFromKeys } from "@/ui/utils/utils"; -import { ContractAddress, WORLD_CONFIG_ID } from "@bibliothecadao/eternum"; +import { ContractAddress } from "@bibliothecadao/eternum"; import { useComponentValue, useEntityQuery } from "@dojoengine/react"; import { Has, getComponentValue, runQuery } from "@dojoengine/recs"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -22,11 +25,10 @@ const BRIDGE_OUT_DELAY = 60 * 60 * 24 * 2; // 2 days export const Rewards = () => { const { account: { account }, - network: { provider }, setup: { components: { AddressName, - Leaderboard, + LeaderboardEntry, LeaderboardRegistered, events: { GameEnded }, }, @@ -45,34 +47,47 @@ export const Rewards = () => { const getContributions = useGetHyperstructuresWithContributionsFromPlayer(); const getEpochs = useGetPlayerEpochs(); + const getUnregisteredContributions = useGetUnregisteredContributions(); + const getUnregisteredEpochs = useGetUnregisteredEpochs(); const gameEndedEntityId = useEntityQuery([Has(GameEnded)]); + const leaderboardEntry = useComponentValue(LeaderboardEntry, getEntityIdFromKeys([ContractAddress(account.address)])); + const gameEnded = useMemo(() => { return getComponentValue(GameEnded, gameEndedEntityId[0]); }, [gameEndedEntityId]); - const leaderboard = useComponentValue(Leaderboard, getEntityIdFromKeys([WORLD_CONFIG_ID])); - const registerToLeaderboard = useCallback(async () => { setIsLoading(true); - const contributions = Array.from(getContributions()); - const epochs = getEpochs(); - - await register_to_leaderboard({ - signer: account, - hyperstructure_contributed_to: contributions, - hyperstructure_shareholder_epochs: epochs, - }); - setIsLoading(false); + const epochs = getUnregisteredEpochs(); + const contributions = getUnregisteredContributions(); + + try { + await register_to_leaderboard({ + signer: account, + hyperstructure_contributed_to: contributions, + hyperstructure_shareholder_epochs: epochs, + }); + } catch (error) { + console.error("Error registering to leaderboard", error); + } finally { + setIsLoading(false); + } }, [getContributions, getEpochs]); const claimRewards = useCallback(async () => { setIsLoading(true); - await claim_leaderboard_rewards({ - signer: account, - token: env.VITE_LORDS_ADDRESS!, - }); + try { + await claim_leaderboard_rewards({ + signer: account, + token: env.VITE_LORDS_ADDRESS!, + }); + } catch (error) { + console.error("Error claiming rewards", error); + } finally { + setIsLoading(false); + } setIsLoading(false); }, [account]); @@ -147,6 +162,13 @@ export const Rewards = () => {
{Number(formatEther(prizePool)).toFixed(2)} $LORDS
+ +
+
Your registered points
+ +
{Number(leaderboardEntry?.points ?? 0)}
+
+
diff --git a/landing/src/components/modules/app-sidebar.tsx b/landing/src/components/modules/app-sidebar.tsx index cb5092f86..3b861f005 100644 --- a/landing/src/components/modules/app-sidebar.tsx +++ b/landing/src/components/modules/app-sidebar.tsx @@ -8,7 +8,7 @@ import { SidebarMenuItem, } from "@/components/ui/sidebar"; import { Link } from "@tanstack/react-router"; -import { Castle, Gamepad2, Home, PlayCircle, Scale, Sheet, Ship, Twitter } from "lucide-react"; +import { Castle, Coins, Gamepad2, Home, PlayCircle, Scale, Sheet, Ship, Twitter } from "lucide-react"; import { TypeH2 } from "../typography/type-h2"; import { ReactComponent as Discord } from "@/assets/icons/discord.svg"; @@ -20,12 +20,12 @@ const items = [ url: "/", icon: Home, }, + { title: "Claim", url: "/claim", icon: Coins }, { title: "Bridge", url: "/trade", icon: Ship, }, - { title: "Realms", url: "/mint", diff --git a/landing/src/components/modules/season-registration-timer.tsx b/landing/src/components/modules/season-registration-timer.tsx new file mode 100644 index 000000000..a6bd499ce --- /dev/null +++ b/landing/src/components/modules/season-registration-timer.tsx @@ -0,0 +1,45 @@ +import { useLeaderboardStatus } from "@/hooks/usePrizeClaim"; +import { useEffect, useState } from "react"; + +export const SeasonRegistrationTimer = () => { + const [timeLeft, setTimeLeft] = useState({ hours: "00", minutes: "00", seconds: "00" }); + + const { leaderboard } = useLeaderboardStatus(); + + const registrationEnd = leaderboard?.registration_end_timestamp; + + useEffect(() => { + if (!registrationEnd) return; + + const timer = setInterval(() => { + const now = Math.floor(Date.now() / 1000); + const end = Number(registrationEnd); + if (now >= end) { + setTimeLeft({ hours: "00", minutes: "00", seconds: "00" }); + clearInterval(timer); + return; + } + + const diff = end - now; + const hours = Math.floor(diff / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = diff % 60; + setTimeLeft({ + hours: String(hours).padStart(2, "0"), + minutes: String(minutes).padStart(2, "0"), + seconds: String(seconds).padStart(2, "0"), + }); + }, 1000); + + return () => clearInterval(timer); + }, [registrationEnd]); + + return ( +
+

Registration Countdown:

+
+ {timeLeft.hours}:{timeLeft.minutes}:{timeLeft.seconds} +
+
+ ); +}; diff --git a/landing/src/components/modules/top-navigation-view.tsx b/landing/src/components/modules/top-navigation-view.tsx index a1b0c6fa2..4807303ac 100644 --- a/landing/src/components/modules/top-navigation-view.tsx +++ b/landing/src/components/modules/top-navigation-view.tsx @@ -10,6 +10,7 @@ import { Button } from "../ui/button"; import { ResourceIcon } from "../ui/elements/ResourceIcon"; import { SidebarTrigger } from "../ui/sidebar"; import { ModeToggle } from "./mode-toggle"; +import { SeasonRegistrationTimer } from "./season-registration-timer"; import { SeasonStartTimer } from "./season-start-timer"; interface TopNavigationViewProps { @@ -63,6 +64,7 @@ export const TopNavigationView = ({
+
{!isConnected ? ( <> diff --git a/landing/src/dojo/createSystemCalls.ts b/landing/src/dojo/createSystemCalls.ts index ac226a68a..474c121de 100644 --- a/landing/src/dojo/createSystemCalls.ts +++ b/landing/src/dojo/createSystemCalls.ts @@ -143,6 +143,18 @@ export function createSystemCalls({ provider }: SetupNetworkResult) { return await provider.bridge_finish_withdraw_from_realm(props); }; + const register_to_leaderboard = async (props: SystemProps.RegisterToLeaderboardProps) => { + await provider.register_to_leaderboard(props); + }; + + const end_game = async (props: SystemProps.EndGameProps) => { + await provider.end_game(props); + }; + + const claim_leaderboard_rewards = async (props: SystemProps.ClaimLeaderboardRewardsProps) => { + await provider.claim_leaderboard_rewards(props); + }; + const isLive = async () => { try { await provider.uuid(); @@ -169,6 +181,9 @@ export function createSystemCalls({ provider }: SetupNetworkResult) { bridge_resources_into_realm: withQueueing(withErrorHandling(bridge_resources_into_realm)), bridge_start_withdraw_from_realm: withQueueing(withErrorHandling(bridge_start_withdraw_from_realm)), bridge_finish_withdraw_from_realm: withQueueing(withErrorHandling(bridge_finish_withdraw_from_realm)), + register_to_leaderboard: withQueueing(withErrorHandling(register_to_leaderboard)), + end_game: withQueueing(withErrorHandling(end_game)), + claim_leaderboard_rewards: withQueueing(withErrorHandling(claim_leaderboard_rewards)), }; // TODO: Fix Type diff --git a/landing/src/dojo/modelManager/leaderboard/LeaderboardManager.ts b/landing/src/dojo/modelManager/leaderboard/LeaderboardManager.ts index b5a5aa936..5e32a84a2 100644 --- a/landing/src/dojo/modelManager/leaderboard/LeaderboardManager.ts +++ b/landing/src/dojo/modelManager/leaderboard/LeaderboardManager.ts @@ -113,6 +113,8 @@ export class LeaderboardManager { const contribution = getComponentValue(this.dojoResult.setup.components.Contribution, contributionEntityId); if (!contribution) return; + if (this.hasClaimedReward(contribution.player_address)) return; + const effectiveContribution = (Number(contribution.amount) * RESOURCE_RARITY[contribution.resource_type as ResourcesIds]!) / configManager.getResourcePrecision(); @@ -140,7 +142,9 @@ export class LeaderboardManager { ); const epochEndTimestamp = - nextEpoch?.start_timestamp ?? season.is_over ? season.ended_at : BigInt(currentTimestamp); + season.is_over && nextEpoch === undefined + ? season.ended_at + : nextEpoch?.start_timestamp ?? BigInt(currentTimestamp); const epochDuration = epochEndTimestamp - epoch.start_timestamp; const nbOfCycles = Number(epochDuration) / ClientConfigManager.instance().getTick(TickIds.Default); @@ -153,6 +157,8 @@ export class LeaderboardManager { owner_address = ContractAddress(owner_address); percentage = Number(percentage) / 10_000; + if (this.hasClaimedReward(owner_address)) return; + const previousPoints = keyPointsMap.get(getKey(owner_address)) || 0; const userShare = totalPoints * percentage; const newPointsForPlayer = previousPoints + userShare; @@ -161,9 +167,18 @@ export class LeaderboardManager { }); } }); + return true; } + public hasClaimedReward(playerAddress: ContractAddress) { + const claimed = getComponentValue( + this.dojoResult.setup.components.LeaderboardRewardClaimed, + getEntityIdFromKeys([playerAddress]), + ); + return claimed?.claimed; + } + public getAddressShares(playerAddress: ContractAddress, hyperstructureEntityId: ID) { const hyperstructure = getComponentValue( this.dojoResult.setup.components.Hyperstructure, diff --git a/landing/src/hooks/gql/gql.ts b/landing/src/hooks/gql/gql.ts index dd58633a6..dd2f28838 100644 --- a/landing/src/hooks/gql/gql.ts +++ b/landing/src/hooks/gql/gql.ts @@ -1,5 +1,7 @@ /* eslint-disable */ -import * as types from "./graphql"; +import * as types from './graphql'; + + /** * Map of all GraphQL operations in the project. @@ -13,72 +15,84 @@ import * as types from "./graphql"; * Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size */ const documents = { - "\n query getCapacitySpeedConfig($category: Enum!, $entityType: u32!) {\n s0EternumCapacityConfigModels(where: {category: $category }) {\n edges{\n node {\n weight_gram\n }\n }\n }\n s0EternumSpeedConfigModels(where: {entity_type: $entityType }) {\n edges{\n node {\n sec_per_km\n }\n }\n }\n }\n": - types.GetCapacitySpeedConfigDocument, - "\n query getEternumOwnerRealmIds($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }, limit: 1000) {\n edges {\n node {\n address\n entity_id\n entity {\n models {\n __typename\n ... on s0_eternum_Realm {\n realm_id\n }\n }\n }\n }\n }\n }\n }\n": - types.GetEternumOwnerRealmIdsDocument, - "\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n": - types.GetEternumEntityOwnerDocument, - "\n query getAccountTokens($accountAddress: String!) {\n tokenBalances(accountAddress: $accountAddress, limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n": - types.GetAccountTokensDocument, - '\n query getERC721Mints {\n tokenTransfers(accountAddress: "0x0", limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n': - types.GetErc721MintsDocument, - "\n query eternumStatistics {\n s0EternumAddressNameModels {\n totalCount\n }\n s0EternumHyperstructureModels {\n totalCount\n }\n s0EternumRealmModels {\n totalCount\n }\n s0EternumFragmentMineDiscoveredModels {\n totalCount\n }\n }\n": - types.EternumStatisticsDocument, - "\n query getEntityPosition($entityIds: [u32!]!) {\n s0EternumPositionModels(where: { entity_idIN: $entityIds }) {\n edges {\n node {\n x\n y\n entity_id\n entity {\n __typename\n }\n }\n }\n }\n }\n": - types.GetEntityPositionDocument, - "\n query getEntitiesResources($entityIds: [u32!]!) {\n s0EternumResourceModels(\n where: { \n entity_idIN: $entityIds\n }\n limit: 100\n ) {\n edges {\n node {\n entity_id\n resource_type\n balance\n entity {\n __typename\n }\n }\n }\n }\n }\n": - types.GetEntitiesResourcesDocument, + "\n query getCapacitySpeedConfig($category: Enum!, $entityType: u32!) {\n s0EternumCapacityConfigModels(where: {category: $category }) {\n edges{\n node {\n weight_gram\n }\n }\n }\n s0EternumSpeedConfigModels(where: {entity_type: $entityType }) {\n edges{\n node {\n sec_per_km\n }\n }\n }\n }\n": types.GetCapacitySpeedConfigDocument, + "\n query getEternumOwnerRealmIds($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }, limit: 1000) {\n edges {\n node {\n address\n entity_id\n entity {\n models {\n __typename\n ... on s0_eternum_Realm {\n realm_id\n }\n }\n }\n }\n }\n }\n }\n": types.GetEternumOwnerRealmIdsDocument, + "\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n": types.GetEternumEntityOwnerDocument, + "\n query getAccountTokens($accountAddress: String!) {\n tokenBalances(accountAddress: $accountAddress, limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n": types.GetAccountTokensDocument, + "\n query getERC721Mints {\n tokenTransfers(accountAddress: \"0x0\", limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n": types.GetErc721MintsDocument, + "\n query eternumStatistics {\n s0EternumAddressNameModels {\n totalCount\n }\n s0EternumHyperstructureModels {\n totalCount\n }\n s0EternumRealmModels {\n totalCount\n }\n s0EternumFragmentMineDiscoveredModels {\n totalCount\n }\n }\n": types.EternumStatisticsDocument, + "\n query hasGameEnded {\n s0EternumGameEndedModels {\n edges {\n node {\n winner_address\n }\n }\n }\n }\n": types.HasGameEndedDocument, + "\n query hasPlayerRegistered($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }) {\n totalCount\n }\n }\n": types.HasPlayerRegisteredDocument, + "\n query hasPlayerClaimed($accountAddress: ContractAddress!) {\n s0EternumLeaderboardRewardClaimedModels(where: { address: $accountAddress }) {\n totalCount\n }\n }\n": types.HasPlayerClaimedDocument, + "\n query getLeaderboardEntry($accountAddress: ContractAddress!) {\n s0EternumLeaderboardEntryModels(where: { address: $accountAddress }) {\n edges {\n node {\n address\n points\n }\n }\n }\n }\n": types.GetLeaderboardEntryDocument, + "\n query getLeaderboard {\n s0EternumLeaderboardModels {\n edges {\n node {\n total_points\n registration_end_timestamp\n total_price_pool {\n Some\n option\n }\n distribution_started\n }\n }\n }\n }\n": types.GetLeaderboardDocument, + "\n query getHyperstructureContributions($accountAddress: ContractAddress!) {\n s0EternumContributionModels(where: { player_address: $accountAddress }, limit: 1000) {\n edges {\n node {\n hyperstructure_entity_id\n amount\n }\n }\n }\n }\n": types.GetHyperstructureContributionsDocument, + "\n query getEpochs {\n s0EternumEpochModels(limit: 1000) {\n edges {\n node {\n owners {\n _0\n _1\n }\n start_timestamp\n hyperstructure_entity_id\n index\n }\n }\n }\n }\n": types.GetEpochsDocument, + "\n query getEntityPosition($entityIds: [u32!]!) {\n s0EternumPositionModels(where: { entity_idIN: $entityIds }) {\n edges {\n node {\n x\n y\n entity_id\n entity {\n __typename\n }\n }\n }\n }\n }\n": types.GetEntityPositionDocument, + "\n query getEntitiesResources($entityIds: [u32!]!) {\n s0EternumResourceModels(\n where: { \n entity_idIN: $entityIds\n }\n limit: 100\n ) {\n edges {\n node {\n entity_id\n resource_type\n balance\n entity {\n __typename\n }\n }\n }\n }\n }\n": types.GetEntitiesResourcesDocument, }; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query getCapacitySpeedConfig($category: Enum!, $entityType: u32!) {\n s0EternumCapacityConfigModels(where: {category: $category }) {\n edges{\n node {\n weight_gram\n }\n }\n }\n s0EternumSpeedConfigModels(where: {entity_type: $entityType }) {\n edges{\n node {\n sec_per_km\n }\n }\n }\n }\n", -): typeof import("./graphql").GetCapacitySpeedConfigDocument; +export function graphql(source: "\n query getCapacitySpeedConfig($category: Enum!, $entityType: u32!) {\n s0EternumCapacityConfigModels(where: {category: $category }) {\n edges{\n node {\n weight_gram\n }\n }\n }\n s0EternumSpeedConfigModels(where: {entity_type: $entityType }) {\n edges{\n node {\n sec_per_km\n }\n }\n }\n }\n"): typeof import('./graphql').GetCapacitySpeedConfigDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query getEternumOwnerRealmIds($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }, limit: 1000) {\n edges {\n node {\n address\n entity_id\n entity {\n models {\n __typename\n ... on s0_eternum_Realm {\n realm_id\n }\n }\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetEternumOwnerRealmIdsDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetEternumEntityOwnerDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query getAccountTokens($accountAddress: String!) {\n tokenBalances(accountAddress: $accountAddress, limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetAccountTokensDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query getERC721Mints {\n tokenTransfers(accountAddress: \"0x0\", limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetErc721MintsDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query eternumStatistics {\n s0EternumAddressNameModels {\n totalCount\n }\n s0EternumHyperstructureModels {\n totalCount\n }\n s0EternumRealmModels {\n totalCount\n }\n s0EternumFragmentMineDiscoveredModels {\n totalCount\n }\n }\n"): typeof import('./graphql').EternumStatisticsDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query hasGameEnded {\n s0EternumGameEndedModels {\n edges {\n node {\n winner_address\n }\n }\n }\n }\n"): typeof import('./graphql').HasGameEndedDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query hasPlayerRegistered($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }) {\n totalCount\n }\n }\n"): typeof import('./graphql').HasPlayerRegisteredDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query getEternumOwnerRealmIds($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }, limit: 1000) {\n edges {\n node {\n address\n entity_id\n entity {\n models {\n __typename\n ... on s0_eternum_Realm {\n realm_id\n }\n }\n }\n }\n }\n }\n }\n", -): typeof import("./graphql").GetEternumOwnerRealmIdsDocument; +export function graphql(source: "\n query hasPlayerClaimed($accountAddress: ContractAddress!) {\n s0EternumLeaderboardRewardClaimedModels(where: { address: $accountAddress }) {\n totalCount\n }\n }\n"): typeof import('./graphql').HasPlayerClaimedDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n", -): typeof import("./graphql").GetEternumEntityOwnerDocument; +export function graphql(source: "\n query getLeaderboardEntry($accountAddress: ContractAddress!) {\n s0EternumLeaderboardEntryModels(where: { address: $accountAddress }) {\n edges {\n node {\n address\n points\n }\n }\n }\n }\n"): typeof import('./graphql').GetLeaderboardEntryDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query getAccountTokens($accountAddress: String!) {\n tokenBalances(accountAddress: $accountAddress, limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n", -): typeof import("./graphql").GetAccountTokensDocument; +export function graphql(source: "\n query getLeaderboard {\n s0EternumLeaderboardModels {\n edges {\n node {\n total_points\n registration_end_timestamp\n total_price_pool {\n Some\n option\n }\n distribution_started\n }\n }\n }\n }\n"): typeof import('./graphql').GetLeaderboardDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: '\n query getERC721Mints {\n tokenTransfers(accountAddress: "0x0", limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n', -): typeof import("./graphql").GetErc721MintsDocument; +export function graphql(source: "\n query getHyperstructureContributions($accountAddress: ContractAddress!) {\n s0EternumContributionModels(where: { player_address: $accountAddress }, limit: 1000) {\n edges {\n node {\n hyperstructure_entity_id\n amount\n }\n }\n }\n }\n"): typeof import('./graphql').GetHyperstructureContributionsDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query eternumStatistics {\n s0EternumAddressNameModels {\n totalCount\n }\n s0EternumHyperstructureModels {\n totalCount\n }\n s0EternumRealmModels {\n totalCount\n }\n s0EternumFragmentMineDiscoveredModels {\n totalCount\n }\n }\n", -): typeof import("./graphql").EternumStatisticsDocument; +export function graphql(source: "\n query getEpochs {\n s0EternumEpochModels(limit: 1000) {\n edges {\n node {\n owners {\n _0\n _1\n }\n start_timestamp\n hyperstructure_entity_id\n index\n }\n }\n }\n }\n"): typeof import('./graphql').GetEpochsDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query getEntityPosition($entityIds: [u32!]!) {\n s0EternumPositionModels(where: { entity_idIN: $entityIds }) {\n edges {\n node {\n x\n y\n entity_id\n entity {\n __typename\n }\n }\n }\n }\n }\n", -): typeof import("./graphql").GetEntityPositionDocument; +export function graphql(source: "\n query getEntityPosition($entityIds: [u32!]!) {\n s0EternumPositionModels(where: { entity_idIN: $entityIds }) {\n edges {\n node {\n x\n y\n entity_id\n entity {\n __typename\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetEntityPositionDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql( - source: "\n query getEntitiesResources($entityIds: [u32!]!) {\n s0EternumResourceModels(\n where: { \n entity_idIN: $entityIds\n }\n limit: 100\n ) {\n edges {\n node {\n entity_id\n resource_type\n balance\n entity {\n __typename\n }\n }\n }\n }\n }\n", -): typeof import("./graphql").GetEntitiesResourcesDocument; +export function graphql(source: "\n query getEntitiesResources($entityIds: [u32!]!) {\n s0EternumResourceModels(\n where: { \n entity_idIN: $entityIds\n }\n limit: 100\n ) {\n edges {\n node {\n entity_id\n resource_type\n balance\n entity {\n __typename\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetEntitiesResourcesDocument; + export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/landing/src/hooks/gql/graphql.ts b/landing/src/hooks/gql/graphql.ts index 34c0aceaa..422555120 100644 --- a/landing/src/hooks/gql/graphql.ts +++ b/landing/src/hooks/gql/graphql.ts @@ -7303,6 +7303,49 @@ export type EternumStatisticsQueryVariables = Exact<{ [key: string]: never; }>; export type EternumStatisticsQuery = { __typename?: 'World__Query', s0EternumAddressNameModels?: { __typename?: 's0_eternum_AddressNameConnection', totalCount: number } | null, s0EternumHyperstructureModels?: { __typename?: 's0_eternum_HyperstructureConnection', totalCount: number } | null, s0EternumRealmModels?: { __typename?: 's0_eternum_RealmConnection', totalCount: number } | null, s0EternumFragmentMineDiscoveredModels?: { __typename?: 's0_eternum_FragmentMineDiscoveredConnection', totalCount: number } | null }; +export type HasGameEndedQueryVariables = Exact<{ [key: string]: never; }>; + + +export type HasGameEndedQuery = { __typename?: 'World__Query', s0EternumGameEndedModels?: { __typename?: 's0_eternum_GameEndedConnection', edges?: Array<{ __typename?: 's0_eternum_GameEndedEdge', node?: { __typename?: 's0_eternum_GameEnded', winner_address?: any | null } | null } | null> | null } | null }; + +export type HasPlayerRegisteredQueryVariables = Exact<{ + accountAddress: Scalars['ContractAddress']['input']; +}>; + + +export type HasPlayerRegisteredQuery = { __typename?: 'World__Query', s0EternumOwnerModels?: { __typename?: 's0_eternum_OwnerConnection', totalCount: number } | null }; + +export type HasPlayerClaimedQueryVariables = Exact<{ + accountAddress: Scalars['ContractAddress']['input']; +}>; + + +export type HasPlayerClaimedQuery = { __typename?: 'World__Query', s0EternumLeaderboardRewardClaimedModels?: { __typename?: 's0_eternum_LeaderboardRewardClaimedConnection', totalCount: number } | null }; + +export type GetLeaderboardEntryQueryVariables = Exact<{ + accountAddress: Scalars['ContractAddress']['input']; +}>; + + +export type GetLeaderboardEntryQuery = { __typename?: 'World__Query', s0EternumLeaderboardEntryModels?: { __typename?: 's0_eternum_LeaderboardEntryConnection', edges?: Array<{ __typename?: 's0_eternum_LeaderboardEntryEdge', node?: { __typename?: 's0_eternum_LeaderboardEntry', address?: any | null, points?: any | null } | null } | null> | null } | null }; + +export type GetLeaderboardQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetLeaderboardQuery = { __typename?: 'World__Query', s0EternumLeaderboardModels?: { __typename?: 's0_eternum_LeaderboardConnection', edges?: Array<{ __typename?: 's0_eternum_LeaderboardEdge', node?: { __typename?: 's0_eternum_Leaderboard', total_points?: any | null, registration_end_timestamp?: any | null, distribution_started?: any | null, total_price_pool?: { __typename?: 's0_eternum_Optionu256', Some?: any | null, option?: any | null } | null } | null } | null> | null } | null }; + +export type GetHyperstructureContributionsQueryVariables = Exact<{ + accountAddress: Scalars['ContractAddress']['input']; +}>; + + +export type GetHyperstructureContributionsQuery = { __typename?: 'World__Query', s0EternumContributionModels?: { __typename?: 's0_eternum_ContributionConnection', edges?: Array<{ __typename?: 's0_eternum_ContributionEdge', node?: { __typename?: 's0_eternum_Contribution', hyperstructure_entity_id?: any | null, amount?: any | null } | null } | null> | null } | null }; + +export type GetEpochsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetEpochsQuery = { __typename?: 'World__Query', s0EternumEpochModels?: { __typename?: 's0_eternum_EpochConnection', edges?: Array<{ __typename?: 's0_eternum_EpochEdge', node?: { __typename?: 's0_eternum_Epoch', start_timestamp?: any | null, hyperstructure_entity_id?: any | null, index?: any | null, owners?: Array<{ __typename?: 's0_eternum_ContractAddressu16', _0?: any | null, _1?: any | null } | null> | null } | null } | null> | null } | null }; + export type GetEntityPositionQueryVariables = Exact<{ entityIds: Array | Scalars['u32']['input']; }>; @@ -7459,6 +7502,92 @@ export const EternumStatisticsDocument = new TypedDocumentString(` } } `) as unknown as TypedDocumentString; +export const HasGameEndedDocument = new TypedDocumentString(` + query hasGameEnded { + s0EternumGameEndedModels { + edges { + node { + winner_address + } + } + } +} + `) as unknown as TypedDocumentString; +export const HasPlayerRegisteredDocument = new TypedDocumentString(` + query hasPlayerRegistered($accountAddress: ContractAddress!) { + s0EternumOwnerModels(where: {address: $accountAddress}) { + totalCount + } +} + `) as unknown as TypedDocumentString; +export const HasPlayerClaimedDocument = new TypedDocumentString(` + query hasPlayerClaimed($accountAddress: ContractAddress!) { + s0EternumLeaderboardRewardClaimedModels(where: {address: $accountAddress}) { + totalCount + } +} + `) as unknown as TypedDocumentString; +export const GetLeaderboardEntryDocument = new TypedDocumentString(` + query getLeaderboardEntry($accountAddress: ContractAddress!) { + s0EternumLeaderboardEntryModels(where: {address: $accountAddress}) { + edges { + node { + address + points + } + } + } +} + `) as unknown as TypedDocumentString; +export const GetLeaderboardDocument = new TypedDocumentString(` + query getLeaderboard { + s0EternumLeaderboardModels { + edges { + node { + total_points + registration_end_timestamp + total_price_pool { + Some + option + } + distribution_started + } + } + } +} + `) as unknown as TypedDocumentString; +export const GetHyperstructureContributionsDocument = new TypedDocumentString(` + query getHyperstructureContributions($accountAddress: ContractAddress!) { + s0EternumContributionModels( + where: {player_address: $accountAddress} + limit: 1000 + ) { + edges { + node { + hyperstructure_entity_id + amount + } + } + } +} + `) as unknown as TypedDocumentString; +export const GetEpochsDocument = new TypedDocumentString(` + query getEpochs { + s0EternumEpochModels(limit: 1000) { + edges { + node { + owners { + _0 + _1 + } + start_timestamp + hyperstructure_entity_id + index + } + } + } +} + `) as unknown as TypedDocumentString; export const GetEntityPositionDocument = new TypedDocumentString(` query getEntityPosition($entityIds: [u32!]!) { s0EternumPositionModels(where: {entity_idIN: $entityIds}) { diff --git a/landing/src/hooks/query/leaderboard.tsx b/landing/src/hooks/query/leaderboard.tsx new file mode 100644 index 000000000..a44d64aa9 --- /dev/null +++ b/landing/src/hooks/query/leaderboard.tsx @@ -0,0 +1,91 @@ +import { graphql } from "../gql"; + +export const GET_GAME_WINNER = graphql(` + query hasGameEnded { + s0EternumGameEndedModels { + edges { + node { + winner_address + } + } + } + } +`); + +export const GET_PLAYER_HAS_REGISTRED = graphql(` + query hasPlayerRegistered($accountAddress: ContractAddress!) { + s0EternumOwnerModels(where: { address: $accountAddress }) { + totalCount + } + } +`); + +export const GET_PLAYER_HAS_CLAIMED = graphql(` + query hasPlayerClaimed($accountAddress: ContractAddress!) { + s0EternumLeaderboardRewardClaimedModels(where: { address: $accountAddress }) { + totalCount + } + } +`); + +export const GET_LEADERBOARD_ENTRY = graphql(` + query getLeaderboardEntry($accountAddress: ContractAddress!) { + s0EternumLeaderboardEntryModels(where: { address: $accountAddress }) { + edges { + node { + address + points + } + } + } + } +`); + +export const GET_LEADERBOARD = graphql(` + query getLeaderboard { + s0EternumLeaderboardModels { + edges { + node { + total_points + registration_end_timestamp + total_price_pool { + Some + option + } + distribution_started + } + } + } + } +`); + +export const GET_PLAYER_HYPERSTRUCTURE_CONTRIBUTIONS = graphql(` + query getHyperstructureContributions($accountAddress: ContractAddress!) { + s0EternumContributionModels(where: { player_address: $accountAddress }, limit: 1000) { + edges { + node { + hyperstructure_entity_id + amount + } + } + } + } +`); + +export const GET_HYPERSTRUCTURE_EPOCHS = graphql(` + query getEpochs { + s0EternumEpochModels(limit: 1000) { + edges { + node { + owners { + _0 + _1 + } + start_timestamp + hyperstructure_entity_id + index + } + } + } + } +`); diff --git a/landing/src/hooks/usePrizeClaim.tsx b/landing/src/hooks/usePrizeClaim.tsx new file mode 100644 index 000000000..5bc075317 --- /dev/null +++ b/landing/src/hooks/usePrizeClaim.tsx @@ -0,0 +1,97 @@ +import { useQuery } from "@tanstack/react-query"; +import { useMemo } from "react"; +import { execute } from "./gql/execute"; +import { + GET_GAME_WINNER, + GET_HYPERSTRUCTURE_EPOCHS, + GET_LEADERBOARD, + GET_LEADERBOARD_ENTRY, + GET_PLAYER_HAS_CLAIMED, + GET_PLAYER_HYPERSTRUCTURE_CONTRIBUTIONS, +} from "./query/leaderboard"; + +export const useLeaderboardEntry = (playerAddress: string) => { + const { data, isLoading } = useQuery({ + queryKey: ["address", playerAddress], + queryFn: () => execute(GET_LEADERBOARD_ENTRY, { accountAddress: playerAddress }), + refetchInterval: 10_000, + }); + + const points = data?.s0EternumLeaderboardEntryModels?.edges?.[0]?.node?.points ?? 0; + + return { points, isLoading }; +}; + +export const useLeaderboardStatus = () => { + const { data, isLoading } = useQuery({ + queryKey: ["leaderboard"], + queryFn: () => execute(GET_LEADERBOARD), + refetchInterval: 10_000, + }); + + const leaderboard = data?.s0EternumLeaderboardModels?.edges?.[0]?.node ?? null; + + return { leaderboard, isLoading }; +}; + +export const useHasPlayerClaimed = (playerAddress: string) => { + const { data, isLoading } = useQuery({ + queryKey: ["address", playerAddress], + queryFn: () => execute(GET_PLAYER_HAS_CLAIMED, { accountAddress: playerAddress }), + refetchInterval: 10_000, + }); + + const hasClaimed = (data?.s0EternumLeaderboardRewardClaimedModels?.totalCount || 0) > 0; + + return { hasClaimed, isLoading }; +}; + +export const useGameWinner = () => { + const { data, isLoading } = useQuery({ + queryKey: ["gameEnded"], + queryFn: () => execute(GET_GAME_WINNER), + refetchInterval: 10_000, + }); + + const winnerAddress = data?.s0EternumGameEndedModels?.edges?.[0]?.node?.winner_address ?? null; + + return { winnerAddress, isLoading }; +}; + +export const useGetPlayerHyperstructureContributions = (playerAddress: string) => { + const { data, isLoading } = useQuery({ + queryKey: ["player_address", playerAddress], + queryFn: () => execute(GET_PLAYER_HYPERSTRUCTURE_CONTRIBUTIONS, { accountAddress: playerAddress }), + refetchInterval: 10_000, + }); + + const hyperstructures = + data?.s0EternumContributionModels?.edges?.map((edge) => edge?.node?.hyperstructure_entity_id) ?? ([] as number[]); + + return { hyperstructures: [...new Set(hyperstructures)], isLoading }; +}; + +export const useGetEpochs = (playerAddress: string) => { + const { data, isLoading } = useQuery({ + queryKey: ["epochs"], + queryFn: () => execute(GET_HYPERSTRUCTURE_EPOCHS), + refetchInterval: 10_000, + }); + + const epochs = useMemo(() => { + if (!data?.s0EternumEpochModels?.edges) return []; + + return data?.s0EternumEpochModels?.edges + ?.map((edge) => { + if (edge?.node?.owners?.find((owner) => owner?._0 === playerAddress)) { + return { + hyperstructure_entity_id: Number(edge?.node.hyperstructure_entity_id), + epoch: edge?.node.index, + }; + } + }) + .filter((epoch): epoch is { hyperstructure_entity_id: number; epoch: number } => epoch !== undefined); + }, [data, playerAddress]); + + return { epochs, isLoading }; +}; diff --git a/landing/src/routeTree.gen.ts b/landing/src/routeTree.gen.ts index 19cd16b5d..e46592564 100644 --- a/landing/src/routeTree.gen.ts +++ b/landing/src/routeTree.gen.ts @@ -8,147 +8,181 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute } from '@tanstack/react-router' // Import Routes -import { Route as rootRoute } from "./routes/__root"; -import { Route as MyEmpireImport } from "./routes/my-empire"; +import { Route as rootRoute } from './routes/__root' +import { Route as MyEmpireImport } from './routes/my-empire' // Create Virtual Routes -const TradeLazyImport = createFileRoute("/trade")(); -const SeasonPassesLazyImport = createFileRoute("/season-passes")(); -const MintLazyImport = createFileRoute("/mint")(); -const IndexLazyImport = createFileRoute("/")(); +const TradeLazyImport = createFileRoute('/trade')() +const SeasonPassesLazyImport = createFileRoute('/season-passes')() +const MintLazyImport = createFileRoute('/mint')() +const ClaimLazyImport = createFileRoute('/claim')() +const IndexLazyImport = createFileRoute('/')() // Create/Update Routes const TradeLazyRoute = TradeLazyImport.update({ - id: "/trade", - path: "/trade", + id: '/trade', + path: '/trade', getParentRoute: () => rootRoute, -} as any).lazy(() => import("./routes/trade.lazy").then((d) => d.Route)); +} as any).lazy(() => import('./routes/trade.lazy').then((d) => d.Route)) const SeasonPassesLazyRoute = SeasonPassesLazyImport.update({ - id: "/season-passes", - path: "/season-passes", + id: '/season-passes', + path: '/season-passes', getParentRoute: () => rootRoute, -} as any).lazy(() => import("./routes/season-passes.lazy").then((d) => d.Route)); +} as any).lazy(() => import('./routes/season-passes.lazy').then((d) => d.Route)) const MintLazyRoute = MintLazyImport.update({ - id: "/mint", - path: "/mint", + id: '/mint', + path: '/mint', getParentRoute: () => rootRoute, -} as any).lazy(() => import("./routes/mint.lazy").then((d) => d.Route)); +} as any).lazy(() => import('./routes/mint.lazy').then((d) => d.Route)) + +const ClaimLazyRoute = ClaimLazyImport.update({ + id: '/claim', + path: '/claim', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/claim.lazy').then((d) => d.Route)) const MyEmpireRoute = MyEmpireImport.update({ - id: "/my-empire", - path: "/my-empire", + id: '/my-empire', + path: '/my-empire', getParentRoute: () => rootRoute, -} as any); +} as any) const IndexLazyRoute = IndexLazyImport.update({ - id: "/", - path: "/", + id: '/', + path: '/', getParentRoute: () => rootRoute, -} as any).lazy(() => import("./routes/index.lazy").then((d) => d.Route)); +} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route)) // Populate the FileRoutesByPath interface -declare module "@tanstack/react-router" { +declare module '@tanstack/react-router' { interface FileRoutesByPath { - "/": { - id: "/"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof IndexLazyImport; - parentRoute: typeof rootRoute; - }; - "/my-empire": { - id: "/my-empire"; - path: "/my-empire"; - fullPath: "/my-empire"; - preLoaderRoute: typeof MyEmpireImport; - parentRoute: typeof rootRoute; - }; - "/mint": { - id: "/mint"; - path: "/mint"; - fullPath: "/mint"; - preLoaderRoute: typeof MintLazyImport; - parentRoute: typeof rootRoute; - }; - "/season-passes": { - id: "/season-passes"; - path: "/season-passes"; - fullPath: "/season-passes"; - preLoaderRoute: typeof SeasonPassesLazyImport; - parentRoute: typeof rootRoute; - }; - "/trade": { - id: "/trade"; - path: "/trade"; - fullPath: "/trade"; - preLoaderRoute: typeof TradeLazyImport; - parentRoute: typeof rootRoute; - }; + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexLazyImport + parentRoute: typeof rootRoute + } + '/my-empire': { + id: '/my-empire' + path: '/my-empire' + fullPath: '/my-empire' + preLoaderRoute: typeof MyEmpireImport + parentRoute: typeof rootRoute + } + '/claim': { + id: '/claim' + path: '/claim' + fullPath: '/claim' + preLoaderRoute: typeof ClaimLazyImport + parentRoute: typeof rootRoute + } + '/mint': { + id: '/mint' + path: '/mint' + fullPath: '/mint' + preLoaderRoute: typeof MintLazyImport + parentRoute: typeof rootRoute + } + '/season-passes': { + id: '/season-passes' + path: '/season-passes' + fullPath: '/season-passes' + preLoaderRoute: typeof SeasonPassesLazyImport + parentRoute: typeof rootRoute + } + '/trade': { + id: '/trade' + path: '/trade' + fullPath: '/trade' + preLoaderRoute: typeof TradeLazyImport + parentRoute: typeof rootRoute + } } } // Create and export the route tree export interface FileRoutesByFullPath { - "/": typeof IndexLazyRoute; - "/my-empire": typeof MyEmpireRoute; - "/mint": typeof MintLazyRoute; - "/season-passes": typeof SeasonPassesLazyRoute; - "/trade": typeof TradeLazyRoute; + '/': typeof IndexLazyRoute + '/my-empire': typeof MyEmpireRoute + '/claim': typeof ClaimLazyRoute + '/mint': typeof MintLazyRoute + '/season-passes': typeof SeasonPassesLazyRoute + '/trade': typeof TradeLazyRoute } export interface FileRoutesByTo { - "/": typeof IndexLazyRoute; - "/my-empire": typeof MyEmpireRoute; - "/mint": typeof MintLazyRoute; - "/season-passes": typeof SeasonPassesLazyRoute; - "/trade": typeof TradeLazyRoute; + '/': typeof IndexLazyRoute + '/my-empire': typeof MyEmpireRoute + '/claim': typeof ClaimLazyRoute + '/mint': typeof MintLazyRoute + '/season-passes': typeof SeasonPassesLazyRoute + '/trade': typeof TradeLazyRoute } export interface FileRoutesById { - __root__: typeof rootRoute; - "/": typeof IndexLazyRoute; - "/my-empire": typeof MyEmpireRoute; - "/mint": typeof MintLazyRoute; - "/season-passes": typeof SeasonPassesLazyRoute; - "/trade": typeof TradeLazyRoute; + __root__: typeof rootRoute + '/': typeof IndexLazyRoute + '/my-empire': typeof MyEmpireRoute + '/claim': typeof ClaimLazyRoute + '/mint': typeof MintLazyRoute + '/season-passes': typeof SeasonPassesLazyRoute + '/trade': typeof TradeLazyRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: "/" | "/my-empire" | "/mint" | "/season-passes" | "/trade"; - fileRoutesByTo: FileRoutesByTo; - to: "/" | "/my-empire" | "/mint" | "/season-passes" | "/trade"; - id: "__root__" | "/" | "/my-empire" | "/mint" | "/season-passes" | "/trade"; - fileRoutesById: FileRoutesById; + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/my-empire' + | '/claim' + | '/mint' + | '/season-passes' + | '/trade' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/my-empire' | '/claim' | '/mint' | '/season-passes' | '/trade' + id: + | '__root__' + | '/' + | '/my-empire' + | '/claim' + | '/mint' + | '/season-passes' + | '/trade' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexLazyRoute: typeof IndexLazyRoute; - MyEmpireRoute: typeof MyEmpireRoute; - MintLazyRoute: typeof MintLazyRoute; - SeasonPassesLazyRoute: typeof SeasonPassesLazyRoute; - TradeLazyRoute: typeof TradeLazyRoute; + IndexLazyRoute: typeof IndexLazyRoute + MyEmpireRoute: typeof MyEmpireRoute + ClaimLazyRoute: typeof ClaimLazyRoute + MintLazyRoute: typeof MintLazyRoute + SeasonPassesLazyRoute: typeof SeasonPassesLazyRoute + TradeLazyRoute: typeof TradeLazyRoute } const rootRouteChildren: RootRouteChildren = { IndexLazyRoute: IndexLazyRoute, MyEmpireRoute: MyEmpireRoute, + ClaimLazyRoute: ClaimLazyRoute, MintLazyRoute: MintLazyRoute, SeasonPassesLazyRoute: SeasonPassesLazyRoute, TradeLazyRoute: TradeLazyRoute, -}; +} -export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileTypes(); +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() /* ROUTE_MANIFEST_START { @@ -158,6 +192,7 @@ export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileT "children": [ "/", "/my-empire", + "/claim", "/mint", "/season-passes", "/trade" @@ -169,6 +204,9 @@ export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileT "/my-empire": { "filePath": "my-empire.tsx" }, + "/claim": { + "filePath": "claim.lazy.tsx" + }, "/mint": { "filePath": "mint.lazy.tsx" }, diff --git a/landing/src/routes/claim.lazy.tsx b/landing/src/routes/claim.lazy.tsx new file mode 100644 index 000000000..873f4b513 --- /dev/null +++ b/landing/src/routes/claim.lazy.tsx @@ -0,0 +1,184 @@ +import { Button } from "@/components/ui/button"; +import { lordsAddress } from "@/config"; +import { useDojo } from "@/hooks/context/DojoContext"; +import { + useGameWinner, + useGetEpochs, + useGetPlayerHyperstructureContributions, + useLeaderboardEntry, + useLeaderboardStatus, +} from "@/hooks/usePrizeClaim"; +import { useAccount } from "@starknet-react/core"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { Loader2 } from "lucide-react"; +import { Suspense, useEffect, useState } from "react"; + +const RegistrationCountdown = ({ registrationEnd }: { registrationEnd: string | undefined }) => { + const [timeLeft, setTimeLeft] = useState({ hours: "00", minutes: "00", seconds: "00" }); + + useEffect(() => { + if (!registrationEnd) return; + + const timer = setInterval(() => { + const now = Math.floor(Date.now() / 1000); + const end = Number(registrationEnd); + if (now >= end) { + setTimeLeft({ hours: "00", minutes: "00", seconds: "00" }); + clearInterval(timer); + return; + } + + const diff = end - now; + const hours = Math.floor(diff / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = diff % 60; + setTimeLeft({ + hours: String(hours).padStart(2, "0"), + minutes: String(minutes).padStart(2, "0"), + seconds: String(seconds).padStart(2, "0"), + }); + }, 1000); + + return () => clearInterval(timer); + }, [registrationEnd]); + + return ( +
+

Registration Countdown

+
+ {timeLeft.hours}:{timeLeft.minutes}:{timeLeft.seconds} +
+
+ ); +}; + +export const Route = createLazyFileRoute("/claim")({ + component: Claim, +}); + +function Claim() { + const { + setup: { + systemCalls: { claim_leaderboard_rewards, register_to_leaderboard }, + }, + } = useDojo(); + + const { account, address } = useAccount(); + + const [registerLoading, setRegisterLoading] = useState(false); + const [claimLoading, setClaimLoading] = useState(false); + + const { points, isLoading: isPointsLoading } = useLeaderboardEntry(address || ""); + const { leaderboard, isLoading: isLeaderboardLoading } = useLeaderboardStatus(); + const { winnerAddress, isLoading: isWinnerLoading } = useGameWinner(); + + const hasRegistered = points > 0; + + const registrationEnd = leaderboard?.registration_end_timestamp; + const isRegistrationPeriodActive = registrationEnd && Math.floor(Date.now() / 1000) < Number(registrationEnd); + + const yourShare = Number(leaderboard?.total_points) + ? ((points / Number(leaderboard?.total_points)) * 100).toFixed(2) + : "0"; + + const { hyperstructures, isLoading: isHsLoading } = useGetPlayerHyperstructureContributions(address || ""); + const { epochs, isLoading: isEpochsLoading } = useGetEpochs(address || ""); + + const loading = isPointsLoading || isLeaderboardLoading || isWinnerLoading || isHsLoading || isEpochsLoading; + + const noPoints = hyperstructures.length === 0 && epochs.length === 0; + + const onRegister = async () => { + if (!account || noPoints) return; + setRegisterLoading(true); + await register_to_leaderboard({ + signer: account, + hyperstructure_contributed_to: hyperstructures, + hyperstructure_shareholder_epochs: epochs, + }).finally(() => setRegisterLoading(false)); + }; + + const onClaim = async () => { + if (!account) return; + setClaimLoading(true); + await claim_leaderboard_rewards({ signer: account, token: lordsAddress }).finally(() => setClaimLoading(false)); + }; + + return ( +
+ {loading && ( +
+ +
+ )} +
+
+ Loading...
}> + + +
+
+
+
+
+

+ Total Points Registred: + {Number(leaderboard?.total_points) || 0} +

+ + {address && ( + <> +

+ Your Points: + {Number(points)} +

+

+ Your Share: + {yourShare}% +

+ {winnerAddress && ( +

+ Winner: +
+ {winnerAddress} +

+ )} + + )} +
+ {address && ( +
+ {noPoints ? ( +

You have no points to register or claim

+ ) : ( + <> + + + + )} +
+ )} +
+
+
+
+ +
+
+
+ ); +} diff --git a/landing/src/routes/index.lazy.tsx b/landing/src/routes/index.lazy.tsx index 082f99554..4eede8c04 100644 --- a/landing/src/routes/index.lazy.tsx +++ b/landing/src/routes/index.lazy.tsx @@ -1,6 +1,5 @@ import { AnimatedGrid } from "@/components/modules/animated-grid"; import { DataCard, DataCardProps } from "@/components/modules/data-card"; -import { Leaderboard } from "@/components/modules/leaderboard"; import { PRIZE_POOL_ACHIEVEMENTS, PRIZE_POOL_CONTENT_CREATORS, @@ -35,7 +34,6 @@ interface GridItemType { } function Index() { - const { data, isLoading } = useQuery({ queryKey: ["eternumStatistics"], queryFn: () => execute(GET_ETERNUM_STATTISTICS), @@ -129,14 +127,7 @@ function Index() { return (
, - }, - ]} + items={[...dataCards]} renderItem={(item) => React.isValidElement(item.data) ? item.data : }