diff --git a/.env.example b/.env.example index b2602df..816b7b0 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,2 @@ -NEXT_PUBLIC_SOL_XEN_PROGRAM_ID= NEXT_PUBLIC_SOLANA_RPC_ENDPOINT= NEXT_PUBLIC_API_ENDPOINT= diff --git a/app/globals.css b/app/globals.css index 76de95a..fe9d7c9 100644 --- a/app/globals.css +++ b/app/globals.css @@ -30,7 +30,7 @@ body { rgb(var(--background-start-rgb)); #background-image-overlay { - opacity: 0.3; + opacity: 0.2; } [data-theme="dark"], [data-theme="synthwave"], @@ -46,7 +46,7 @@ body { [data-theme="sunset"], [data-theme="halloween"] { #background-image-overlay { - opacity: 0.06; + opacity: 0.12; } } } @@ -61,8 +61,8 @@ body { 0% { opacity: 0; } - 50% { - opacity: 1; + 100% { + opacity: 0.90; } } diff --git a/app/hooks/LeaderboardDataHook.tsx b/app/hooks/LeaderboardDataHook.tsx index 2c07e26..8ef67d5 100644 --- a/app/hooks/LeaderboardDataHook.tsx +++ b/app/hooks/LeaderboardDataHook.tsx @@ -14,6 +14,7 @@ export function useLeaderboardData() { (data: LeaderboardEntry[]) => void, ] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [isUpdating, setIsUpdating] = useState(true); const accountType = useAccountType() as AccountType; const searchParams = useSearchParams(); const [leaderboardIndex, setLeaderboardIndex]: [any, any] = useState< @@ -22,11 +23,13 @@ export function useLeaderboardData() { useEffect(() => { const fetchData = async () => { + setIsUpdating(true); fetchLeaderboardData(accountType).then((data: LeaderboardEntry[]) => { - const idxData = generateLeaderboardIndex(data, accountType); + const idxData = generateLeaderboardIndex(data); setLeaderboardData(data); setLeaderboardIndex(idxData); setIsLoading(false); + setIsUpdating(false); // console.log("Fetched leaderboard data", data, idxData); }); }; diff --git a/app/hooks/SolanaEventsHook.tsx b/app/hooks/SolanaEventsHook.tsx index d43e447..da2b732 100644 --- a/app/hooks/SolanaEventsHook.tsx +++ b/app/hooks/SolanaEventsHook.tsx @@ -1,15 +1,16 @@ "use client"; -import { useEffect, useRef } from "react"; +import {useEffect, useRef, useState} from "react"; import { Connection } from "@solana/web3.js"; import { AnchorProvider, BN, Program, - setProvider, web3, } from "@coral-xyz/anchor"; import * as idl from "@/app/leaderboard/target/idl/sol_xen.json"; +import {fetchStateData} from "@/app/leaderboard/Api"; + interface SolanaEventsContextType { handleEvent?: (event: EventHash) => void; } @@ -24,12 +25,20 @@ export interface EventHash { } export function useSolanaEvents({ handleEvent }: SolanaEventsContextType) { + const [programsIds, setProgramsIds] = useState([]); + function useRefEventListener(fn: any) { const fnRef = useRef(fn); fnRef.current = fn; return fnRef; } + useEffect(() => { + fetchStateData().then((state) => { + setProgramsIds(state.programs); + }) + }, []); + // We can use the custom hook declared above const handleResizeRef = useRefEventListener(handleEvent); @@ -37,25 +46,29 @@ export function useSolanaEvents({ handleEvent }: SolanaEventsContextType) { const connection = new Connection( process.env.NEXT_PUBLIC_SOLANA_RPC_ENDPOINT || "", ); + const provider = new AnchorProvider(connection, null as any); - setProvider(provider); + const programs: Program[] = []; + const listeners: number[] = []; - const idlClone = JSON.parse(JSON.stringify(idl)); - idlClone.address = process.env.NEXT_PUBLIC_SOL_XEN_PROGRAM_ID; - const program = new Program(idlClone as any, provider); + for (let i = 0; i < programsIds.length; i++) { + const idlClone = JSON.parse(JSON.stringify(idl)); + idlClone.address = programsIds[i]; + programs.push(new Program(idlClone as any, provider)); - console.log("Listening to hash events"); - const listener = program.addEventListener("hashEvent", (event: any) => { - // if (handleResizeRef.current) { - handleResizeRef.current(event); - // } - }); + console.log(`Listening to hash events: ${programsIds[i]}`); + listeners[i] = programs[i].addEventListener("hashEvent", (event: EventHash) => { + handleResizeRef.current(event); + }); + } return () => { - console.log("stop listening to hash events"); - program.removeEventListener(listener).then(); + for (let i = 0; i < programsIds.length; i++) { + console.log(`stop listening to hash events: ${programsIds[i]}`); + programs[i].removeEventListener(listeners[i]).then(); + } }; - }, [handleResizeRef]); + }, [programsIds, handleResizeRef]); return handleResizeRef; } diff --git a/app/hooks/StateDataHook.tsx b/app/hooks/StateDataHook.tsx index 5625868..adaaa51 100644 --- a/app/hooks/StateDataHook.tsx +++ b/app/hooks/StateDataHook.tsx @@ -6,7 +6,7 @@ import { State } from "@/app/leaderboard/StateStats"; const initialState: State = { points: BigInt(0), - solXen: 0, + solXen: 0n, hashes: 0, superHashes: 0, txs: 0, @@ -20,6 +20,7 @@ const initialState: State = { minPriorityFee: 0, medianPriorityFee: 0, maxPriorityFee: 0, + programs: [] }; export function useStatsData() { diff --git a/app/leaderboard/AmpBanner.tsx b/app/leaderboard/AmpBanner.tsx index 71882ab..f1f766b 100644 --- a/app/leaderboard/AmpBanner.tsx +++ b/app/leaderboard/AmpBanner.tsx @@ -14,7 +14,7 @@ export default function AmpBanner({ isLoading, stateData }: AmpBannerProps) { return (
0 ? "fade-in" : ""}`} + className={`bg-info/50 z-[1] text-info-content w-full grid grid-cols-2 sm:grid-cols-3 gap-2 h-[45px] md:h-[50px] opacity-0 ${!isLoading && stateData.amp > 0 ? "fade-in" : ""}`} >
diff --git a/app/leaderboard/Api.ts b/app/leaderboard/Api.ts index 9db04c4..0df7d8c 100644 --- a/app/leaderboard/Api.ts +++ b/app/leaderboard/Api.ts @@ -11,9 +11,18 @@ export async function fetchLeaderboardData(accountType: AccountType) { } const out = await data.json(); - - for (const entry of out) { - entry.points = BigInt(entry.points); + if (accountType == AccountType.Solana) { + for (const entry of out) { + entry.solXen = BigInt(entry.solXen); + entry.hashes = BigInt(entry.hashes); + entry.superHashes = BigInt(entry.superHashes); + } + } else { + for (const entry of out) { + entry.points = BigInt(entry.points); + entry.hashes = BigInt(entry.hashes); + entry.superHashes = BigInt(entry.superHashes); + } } return out; @@ -27,20 +36,20 @@ export async function fetchStateData() { } const out = await data.json(); - out.points = BigInt(out.points); + out.solXen = BigInt(out.solXen); + out.txs = BigInt(out.txs); + out.hashes = BigInt(out.hashes); + out.superHashes = BigInt(out.superHashes); return out; } export function generateLeaderboardIndex( leaderboardData: LeaderboardEntry[], - accountType: AccountType | string, ) { return leaderboardData.reduce( (acc, entry, index) => { - const accountKey = - accountType == AccountType.Solana ? entry.solAccount : entry.ethAccount; - acc[accountKey] = index; + acc[entry.account] = index; return acc; }, {} as Record, @@ -64,6 +73,12 @@ export async function fetchLeaderboardEntry(account: string) { if (out?.points) { out.points = BigInt(out.points); } + if (out?.solXen) { + out.solXen = BigInt(out.solXen); + } + out.hashes = BigInt(out.hashes); + out.superHashes = BigInt(out.superHashes); + return out; } @@ -122,6 +137,8 @@ export async function fetchAssociatedEthAccounts( for (const entry of out) { entry.points = BigInt(entry.points); + entry.hashes = BigInt(entry.hashes); + entry.superHashes = BigInt(entry.superHashes); } return out; @@ -145,7 +162,29 @@ export async function fetchAssociatedSolAccounts( const out = await response.json(); for (const entry of out) { - entry.points = BigInt(entry.points); + entry.solXen = BigInt(entry.solXen); + entry.hashes = BigInt(entry.hashes); + entry.superHashes = BigInt(entry.superHashes); + } + + return out; +} + +export async function fetchStateHistory() { + const data = await fetch( + `${process.env.NEXT_PUBLIC_API_ENDPOINT}/state/history`, + ); + + if (!data.ok) { + throw new Error("Error fetching state history data"); + } + + const out = await data.json(); + + for (const entry of out) { + if (entry.solXen) { + entry.solXen = BigInt(entry.solXen); + } } return out; diff --git a/app/leaderboard/Background.tsx b/app/leaderboard/Background.tsx index fbfc2d7..7138039 100644 --- a/app/leaderboard/Background.tsx +++ b/app/leaderboard/Background.tsx @@ -12,7 +12,7 @@ export function Background({ isLoading }: BackgroundProps) { className="fixed h-full w-full left-0 top-0" > Background image(""); const changeSearchBox = (event: React.ChangeEvent) => { setSearchInput(event.target.value); - console.log("Search input", event.target.value); }; - const percentOfState = (points: bigint) => { - if (!stateData) { + const percentOfState = (solXen: bigint): number => { + // @ts-ignore + if (!stateData || stateData.solXen === 0n || stateData.solXen === 0) { return 0; } - if (stateData.points === 0n) { + if (solXen === 0n || !solXen) { return 0; } - return Math.floor(Number((points * 10000n) / stateData.points) / 100); + + return Math.floor(Number((solXen * 10000n) / stateData.solXen) / 100); }; const handleClickAccount = (account: string) => { @@ -111,7 +111,7 @@ export function LeadersTable({ {leaderboardData.map( ( - { rank, solAccount, ethAccount, hashes, superHashes, points }, + { rank, account, hashes, superHashes, solXen }, index, ) => { return ( @@ -119,11 +119,7 @@ export function LeadersTable({ key={rank} className={`cursor-pointer hover`} onClick={() => { - if (accountType == AccountType.Ethereum) { - handleClickAccount(ethAccount); - } else { - handleClickAccount(solAccount); - } + handleClickAccount(account); }} > @@ -133,7 +129,7 @@ export function LeadersTable({ - {accountType == "solana" ? solAccount : ethAccount} + {account}
@@ -159,12 +155,12 @@ export function LeadersTable({ solXEN
- {percentOfState(points) > 0 ? ( + {percentOfState(solXen) > 0 ? (
- {percentOfState(points)}% + {percentOfState(solXen)}%
) : null} - {solXenValue(points)} + {solXenValue(solXen)}
) : null} @@ -183,12 +179,10 @@ export function LeadersTable({ {accountType == AccountType.Solana ? ( - {Intl.NumberFormat("en-US").format( - points / 1_000_000_000n, - )} - {percentOfState(points) > 0 ? ( + {solXenValue(solXen)} + {percentOfState(solXen) > 0 ? (
- {percentOfState(points)}% + {percentOfState(solXen)}%
) : null}
diff --git a/app/leaderboard/StateStat.tsx b/app/leaderboard/StateStat.tsx index dbd9dda..b567689 100644 --- a/app/leaderboard/StateStat.tsx +++ b/app/leaderboard/StateStat.tsx @@ -45,7 +45,7 @@ export default function StateStat({ if (showDetails) { return 10; } - return 50; + return 40; }; const boarderAlpha = (showDetails: boolean) => { diff --git a/app/leaderboard/StateStats.tsx b/app/leaderboard/StateStats.tsx index 313a662..e872047 100644 --- a/app/leaderboard/StateStats.tsx +++ b/app/leaderboard/StateStats.tsx @@ -2,11 +2,11 @@ import React, { useContext, useEffect, useState } from "react"; import "chart.js/auto"; import { ThemeContext } from "@/app/context/ThemeContext"; import StateStat from "@/app/leaderboard/StateStat"; -import { fetchHashEventStats, HashEventStat } from "@/app/leaderboard/Api"; +import {fetchHashEventStats, fetchStateHistory, HashEventStat} from "@/app/leaderboard/Api"; export interface State { points: bigint; - solXen: number; + solXen: bigint; hashes: number; superHashes: number; txs: number; @@ -20,24 +20,7 @@ export interface State { minPriorityFee: number; medianPriorityFee: number; maxPriorityFee: number; -} - -async function fetchStateHistory() { - const data = await fetch( - `${process.env.NEXT_PUBLIC_API_ENDPOINT}/state/history`, - ); - - if (!data.ok) { - throw new Error("Error fetching state history data"); - } - - const out = await data.json(); - - for (const entry of out) { - entry.points = BigInt(entry.points); - } - - return out; + programs: string[]; } export default function StateStats({ @@ -54,8 +37,11 @@ export default function StateStats({ const [hashEventStats, setHashEventStats] = useState([]); const totalSupplyValue = () => { + if (!state.solXen) { + return "0"; + } return Intl.NumberFormat("en-US").format( - Number(state.points / 1_000_000_000n), + Number(state.solXen / 1_000_000_000n), ); }; @@ -145,19 +131,31 @@ export default function StateStats({ {totalSuperHashesValue()} + {/* ({*/} + {/* x: new Date(entry.createdAt),*/} + {/* y: entry.txs,*/} + {/* }))}*/} + {/*>*/} + {/* {txsValue()}*/} + {/**/} + ({ + name="lastAmpSlot" + title="Last AMP Slot" + stateHistory={stateHistory.map((entry) => ({ x: new Date(entry.createdAt), - y: entry.txs, + y: Number(entry.lastAmpSlot), }))} > - {txsValue()} + {lastAmpSlotValue()} + - {/* Number(entry.lastAmpSlot))}*/} - {/*>*/} - {/* {lastAmpSlotValue()}*/} - {/**/} - { if (accountType() == AccountType.Ethereum) { fetchAssociatedSolAccounts(accountAddress).then((data) => { - const idxData = generateLeaderboardIndex(data, AccountType.Solana); - console.log("Fetched associated sol accounts", data, idxData); + const idxData = generateLeaderboardIndex(data); + // console.log("Fetched associated sol accounts", data, idxData); setLeaderboardData(data); setLeaderboardIndex(idxData); setIsAssociatedLoading(false); }); } else { fetchAssociatedEthAccounts(accountAddress).then((data) => { - const idxData = generateLeaderboardIndex(data, AccountType.Ethereum); + const idxData = generateLeaderboardIndex(data); console.log("Fetched associated eth accounts", data, idxData); setLeaderboardData(data); setLeaderboardIndex(idxData); @@ -71,20 +71,23 @@ export function AccountAssociations({ (eventHash.hashes > 0 || eventHash.superhashes > 0) ) { const index = leaderboardIndex[otherAccount]; - leaderboardData[index].points += BigInt( - "0x" + eventHash.points.toString("hex"), - ); - leaderboardData[index].hashes += eventHash.hashes; - leaderboardData[index].superHashes += eventHash.superhashes; + if (leaderboardData[index].solXen != undefined) { + leaderboardData[index].solXen += BigInt( + "0x" + eventHash.points.toString("hex"), + ); + } + leaderboardData[index].hashes += BigInt(eventHash.hashes); + leaderboardData[index].superHashes += BigInt(eventHash.superhashes); setLeaderboardData([...leaderboardData]); } }, [eventHash]); return (
+
Associated {associatedAccountType()} Accounts diff --git a/app/leaderboard/[slug]/AccountCharts.tsx b/app/leaderboard/[slug]/AccountCharts.tsx index 08a09b0..b96c7cf 100644 --- a/app/leaderboard/[slug]/AccountCharts.tsx +++ b/app/leaderboard/[slug]/AccountCharts.tsx @@ -111,10 +111,10 @@ export function AccountCharts({ const truncatedDate = new Date(stat.createdAt); truncatedDate.setMilliseconds(0); truncatedDate.setSeconds(0); - newHashes.set(truncatedDate.getTime(), stat.hashes); - newSuperHashes.set(truncatedDate.getTime(), stat.superHashes); - newSolXen.set(truncatedDate.getTime(), stat.solXen); - newTxs.set(truncatedDate.getTime(), stat.txs); + newHashes.set(truncatedDate.getTime(), Number(stat.hashes)); + newSuperHashes.set(truncatedDate.getTime(), Number(stat.superHashes)); + newSolXen.set(truncatedDate.getTime(), Number(stat.solXen)); + newTxs.set(truncatedDate.getTime(), Number(stat.txs)); } if (firstUpdate.current) { @@ -153,13 +153,13 @@ export function AccountCharts({ return (
Real Time Mining Stats
-
+
diff --git a/app/leaderboard/[slug]/AccountStats.tsx b/app/leaderboard/[slug]/AccountStats.tsx index eff8182..85b61a1 100644 --- a/app/leaderboard/[slug]/AccountStats.tsx +++ b/app/leaderboard/[slug]/AccountStats.tsx @@ -50,11 +50,11 @@ export function AccountStats({ }, [accountAddress]); const solXenValue = () => { - if (!accountData?.points) { + if (!accountData?.solXen) { return "0"; } return Intl.NumberFormat("en-US").format( - Number(accountData.points / BigInt(10 ** 9)), + Number(accountData.solXen / BigInt(10 ** 9)), ); }; @@ -81,7 +81,7 @@ export function AccountStats({ return (
diff --git a/app/leaderboard/[slug]/page.tsx b/app/leaderboard/[slug]/page.tsx index 174f22c..671da8d 100644 --- a/app/leaderboard/[slug]/page.tsx +++ b/app/leaderboard/[slug]/page.tsx @@ -40,18 +40,21 @@ export default function LeaderboardSlug({ ? eventHash.user.toBase58() : "0x" + Buffer.from(eventHash.ethAccount).toString("hex"); - if (account == accountAddress) { + if (account.toLowerCase() == accountAddress.toLowerCase()) { // console.log("Event for account", accountAddress); const newAccountData = Object.assign({}, accountData); - newAccountData.hashes += eventHash.hashes; - newAccountData.superHashes += eventHash.superhashes; + newAccountData.hashes += BigInt(eventHash.hashes); + newAccountData.superHashes += BigInt(eventHash.superhashes); if (eventHash.points > 0) { const points = BigInt("0x" + eventHash.points.toString("hex")); - newAccountData.points += points; + if (newAccountData.points != undefined) { + newAccountData.points += points; + } } setAccountData(newAccountData); + setAccountAddress(newAccountData.account); setEventHash(eventHash); } }, diff --git a/app/leaderboard/loading.jsx b/app/leaderboard/loading.jsx deleted file mode 100644 index 95e8734..0000000 --- a/app/leaderboard/loading.jsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function Loading() { - // You can add any UI inside Loading, including a Skeleton. - return ( -
-
-
- ); -} diff --git a/app/leaderboard/page.tsx b/app/leaderboard/page.tsx index c1c5526..7e00452 100644 --- a/app/leaderboard/page.tsx +++ b/app/leaderboard/page.tsx @@ -10,7 +10,8 @@ import { EventHash, useSolanaEvents } from "@/app/hooks/SolanaEventsHook"; import { AccountType, useAccountType } from "@/app/hooks/AccountTypeHook"; import { useLeaderboardData } from "@/app/hooks/LeaderboardDataHook"; import { useStatsData } from "@/app/hooks/StateDataHook"; -import { useState } from "react"; +import React, { useState } from "react"; +import {Loader} from "@/app/components/Loader"; export default function Leaderboard() { const [ @@ -18,6 +19,7 @@ export default function Leaderboard() { setLeaderboardData, leaderboardIndex, isLeaderboardLoading, + isLeaderBoardUpdating ] = useLeaderboardData(); const accountType = useAccountType() as AccountType; const [stateData, setStateData, isStatsLoadingStats] = useStatsData(); @@ -28,15 +30,18 @@ export default function Leaderboard() { // update the account data when a new event is received useSolanaEvents({ handleEvent: (eventHash: EventHash) => { + const newState = { ...stateData }; const account = accountType == AccountType.Solana ? eventHash.user.toBase58() : "0x" + Buffer.from(eventHash.ethAccount).toString("hex"); // console.log("Event hash", eventHash, account, leaderboardIndex[account]); - stateData.points += BigInt("0x" + eventHash.points.toString("hex")); - stateData.hashes += eventHash.hashes; - stateData.superHashes += eventHash.superhashes; - stateData.txs += 1; + stateData.solXen += BigInt("0x" + eventHash.points.toString("hex")); + newState.hashes += BigInt(eventHash.hashes); + newState.superHashes += BigInt(eventHash.superhashes); + newState.txs += 1n; + setStateData(newState); + if ( leaderboardIndex[account] != undefined && (eventHash.hashes > 0 || @@ -44,11 +49,11 @@ export default function Leaderboard() { eventHash.points > 0) ) { const index = leaderboardIndex[account]; - leaderboardData[index].points += BigInt( + leaderboardData[index].solXen += BigInt( "0x" + eventHash.points.toString("hex"), ); - leaderboardData[index].hashes += eventHash.hashes; - leaderboardData[index].superHashes += eventHash.superhashes; + leaderboardData[index].hashes += BigInt(eventHash.hashes); + leaderboardData[index].superHashes += BigInt(eventHash.superhashes); setLeaderboardData([...leaderboardData]); } }, @@ -56,13 +61,15 @@ export default function Leaderboard() { return (
- {showBackground && } - - + {showBackground && } + +
+ +
@@ -71,7 +78,7 @@ export default function Leaderboard() {
- +
@@ -81,17 +88,28 @@ export default function Leaderboard() { isLoadingStats={isStatsLoadingStats} setShowBackground={setShowBackground} /> +
+
- +
+ +
+ +
- {!isLoading &&