diff --git a/explorer-v2/src/app/api/types.ts b/explorer-v2/src/app/api/types.ts index 7879df4a5a..6ce0059007 100644 --- a/explorer-v2/src/app/api/types.ts +++ b/explorer-v2/src/app/api/types.ts @@ -516,77 +516,77 @@ export type NS_NODE = { node_id: number; node_type: string; original_pledge: number; - rewarding_details: { + rewarding_details?: { cost_params: { interval_operating_cost: { amount: string; denom: string; }; + profit_margin_percent: string; }; - profit_margin_percent: string; delegates: string; last_rewarded_epoch: number; operator: string; total_unit_reward: string; unique_delegations: number; unit_delegation: string; - }; - self_description: { + } | null; + self_description?: { authenticator: { address: string; }; - }; - auxiliary_details: { - accepted_operator_terms_and_conditions: boolean; - announce_ports: { - mix_port: number | null; - verloc_port: number | null; + auxiliary_details: { + accepted_operator_terms_and_conditions: boolean; + announce_ports: { + mix_port: number | null; + verloc_port: number | null; + }; + location: string; }; - location: string; - }; - build_information: { - binary_name: string; - build_timestamp: string; - build_version: string; - cargo_profile: string; - cargo_triple: string; - commit_branch: string; - commit_sha: string; - commit_timestamp: string; - rustc_channel: string; - rustc_version: string; - }; - declared_role: { - entry: boolean; - exit_ipr: boolean; - exit_nr: boolean; - mixnode: boolean; - }; - host_information: { - hostname: string; - ip_address: [string, string]; - keys: { - ed25519: string; - x25519: string; - x25519_noise: string | null; + build_information: { + binary_name: string; + build_timestamp: string; + build_version: string; + cargo_profile: string; + cargo_triple: string; + commit_branch: string; + commit_sha: string; + commit_timestamp: string; + rustc_channel: string; + rustc_version: string; }; - }; - ip_packet_router: { - address: string; - }; - last_polled: string; - mixnet_websockets: { - ws_port: number; - wss_port: number; - }; - network_requester: { - address: string; - uses_exit_policy: boolean; - }; - wireguard: { - port: number; - public_key: string; - }; + declared_role: { + entry: boolean; + exit_ipr: boolean; + exit_nr: boolean; + mixnode: boolean; + }; + host_information: { + hostname: string; + ip_address: [string, string]; + keys: { + ed25519: string; + x25519: string; + x25519_noise: string | null; + }; + }; + ip_packet_router: { + address: string; + }; + last_polled: string; + mixnet_websockets: { + ws_port: number; + wss_port: number; + }; + network_requester: { + address: string; + uses_exit_policy: boolean; + }; + wireguard: { + port: number; + public_key: string; + }; + } | null; total_stake: string; uptime: number; }; diff --git a/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx b/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx index 54cce06fa1..46cd35b5aa 100644 --- a/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx +++ b/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx @@ -3,15 +3,23 @@ import { Card, CardContent, Skeleton, Stack, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import DOMPurify from "isomorphic-dompurify"; -import { fetchEpochRewards, fetchObservatoryNodes } from "../../app/api"; -import type { ExplorerData, IObservatoryNode } from "../../app/api/types"; +import { + fetchEpochRewards, + fetchNSApiNodes, + fetchObservatoryNodes, +} from "../../app/api"; +import type { + ExplorerData, + IObservatoryNode, + NS_NODE, +} from "../../app/api/types"; import { countryName } from "../../utils/countryName"; import NodeTable from "./NodeTable"; // Utility function to calculate node saturation point function getNodeSaturationPoint( totalStake: number, - stakeSaturationPoint: string, + stakeSaturationPoint: string ): number { const saturation = Number.parseFloat(stakeSaturationPoint); @@ -27,16 +35,16 @@ function getNodeSaturationPoint( // Map nodes with rewards data const mappedNymNodes = ( nodes: IObservatoryNode[], - epochRewardsData: ExplorerData["currentEpochRewardsData"], + epochRewardsData: ExplorerData["currentEpochRewardsData"] ) => nodes.map((node) => { const nodeSaturationPoint = getNodeSaturationPoint( node.total_stake, - epochRewardsData.interval.stake_saturation_point, + epochRewardsData.interval.stake_saturation_point ); const cleanMoniker = DOMPurify.sanitize( - node.self_description.moniker, + node.self_description.moniker ).replace(/&/g, "&"); return { @@ -57,6 +65,36 @@ const mappedNymNodes = ( export type MappedNymNodes = ReturnType; export type MappedNymNode = MappedNymNodes[0]; +const mappedNSApiNodes = ( + nodes: NS_NODE[], + epochRewardsData: ExplorerData["currentEpochRewardsData"] +) => + nodes.map((node) => { + const nodeSaturationPoint = getNodeSaturationPoint( + +node.total_stake, + epochRewardsData.interval.stake_saturation_point + ); + + const cleanMoniker = DOMPurify.sanitize(node.description.moniker).replace( + /&/g, + "&" + ); + + return { + name: cleanMoniker, + nodeId: node.node_id, + identity_key: node.identity_key, + countryCode: node.geoip.country || null, + countryName: countryName(node.geoip.country) || null, + profitMarginPercentage: node.rewarding_details + ? +node.rewarding_details.cost_params.profit_margin_percent * 100 + : 0, + owner: node.bonding_address, + stakeSaturation: nodeSaturationPoint, + qualityOfService: +node.uptime * 100, + }; + }); + const NodeTableWithAction = () => { // Use React Query to fetch epoch rewards const { @@ -86,8 +124,21 @@ const NodeTableWithAction = () => { refetchOnMount: false, }); + const { + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, + } = useQuery({ + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, + staleTime: 10 * 60 * 1000, // 10 minutes + refetchOnWindowFocus: false, // Prevents unnecessary refetching + refetchOnReconnect: false, + refetchOnMount: false, + }); + // Handle loading state - if (isEpochLoading || isNodesLoading) { + if (isEpochLoading || isNodesLoading || isNSApiNodesLoading) { return ( @@ -101,7 +152,7 @@ const NodeTableWithAction = () => { } // Handle error state - if (isEpochError || isNodesError) { + if (isEpochError || isNodesError || isNSApiNodesError) { return ( @@ -119,7 +170,8 @@ const NodeTableWithAction = () => { const data = mappedNymNodes(nymNodes || [], epochRewardsData); - return ; + const nsApiNodesData = mappedNSApiNodes(nsApiNodes || [], epochRewardsData); + return ; }; export default NodeTableWithAction; diff --git a/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx b/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx index 7cae2ed050..55631feabd 100644 --- a/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx @@ -1,9 +1,9 @@ "use client"; -import type { IObservatoryNode } from "@/app/api/types"; -import { Skeleton, Stack, Typography } from "@mui/material"; +import type { NS_NODE } from "@/app/api/types"; +import { Skeleton, Stack, Typography, useTheme } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; -import { fetchObservatoryNodes } from "../../app/api"; +import { fetchNSApiNodes } from "../../app/api"; import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; import CopyToClipboard from "../copyToClipboard/CopyToClipboard"; @@ -14,22 +14,25 @@ type Props = { }; export const BasicInfoCard = ({ paramId }: Props) => { - let nodeInfo: IObservatoryNode | undefined; + let nodeInfo: NS_NODE | undefined; const { - data: nymNodes, - isLoading, - isError, + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, refetchOnMount: false, }); - if (isLoading) { + const theme = useTheme(); + const isDarkMode = theme.palette.mode === "dark"; + + if (isNSApiNodesLoading) { return ( @@ -42,10 +45,13 @@ export const BasicInfoCard = ({ paramId }: Props) => { ); } - if (isError || !nymNodes) { + if (!nsApiNodes || isNSApiNodesError) { return ( - + Failed to load node data. @@ -55,16 +61,20 @@ export const BasicInfoCard = ({ paramId }: Props) => { // get node info based on wether it's dentity_key or node_id if (paramId.length > 10) { - nodeInfo = nymNodes.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.identity_key === paramId + ); } else { - nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.node_id === Number(paramId) + ); } if (!nodeInfo) return null; - const selfBond = formatBigNum( - Number(nodeInfo.rewarding_details.operator) / 1_000_000, - ); + const selfBond = nodeInfo.rewarding_details + ? formatBigNum(Number(nodeInfo.rewarding_details.operator) / 1_000_000) + : 0; const selfBondFormatted = `${selfBond} NYM`; return ( @@ -85,9 +95,13 @@ export const BasicInfoCard = ({ paramId }: Props) => { variant="body4" sx={{ wordWrap: "break-word", maxWidth: "90%" }} > - {nodeInfo.bonding_address} + {nodeInfo.bonding_address + ? nodeInfo.bonding_address + : "Node not bonded"} - + {nodeInfo.bonding_address && ( + + )} } /> @@ -113,12 +127,14 @@ export const BasicInfoCard = ({ paramId }: Props) => { } /> - + {nodeInfo.rewarding_details && ( + + )} diff --git a/explorer-v2/src/components/nymNodePageComponents/NodeDataCard.tsx b/explorer-v2/src/components/nymNodePageComponents/NodeDataCard.tsx index ccac2c1920..9abc02850a 100644 --- a/explorer-v2/src/components/nymNodePageComponents/NodeDataCard.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/NodeDataCard.tsx @@ -1,10 +1,10 @@ "use client"; -import type { IObservatoryNode } from "@/app/api/types"; -import { Skeleton, Typography } from "@mui/material"; +import type { NS_NODE } from "@/app/api/types"; +import { Skeleton, Typography, useTheme } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import { format } from "date-fns"; -import { fetchEpochRewards, fetchObservatoryNodes } from "../../app/api"; +import { fetchEpochRewards, fetchNSApiNodes } from "../../app/api"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; @@ -13,7 +13,7 @@ type Props = { }; export const NodeDataCard = ({ paramId }: Props) => { - let nodeInfo: IObservatoryNode | undefined; + let nodeInfo: NS_NODE | undefined; const { data: epochRewardsData, @@ -30,19 +30,22 @@ export const NodeDataCard = ({ paramId }: Props) => { // Fetch node information const { - data: nymNodes, - isLoading, - isError, + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, refetchOnMount: false, }); - if (isEpochLoading || isLoading) { + const theme = useTheme(); + const isDarkMode = theme.palette.mode === "dark"; + + if (isEpochLoading || isNSApiNodesLoading) { return ( @@ -53,10 +56,13 @@ export const NodeDataCard = ({ paramId }: Props) => { ); } - if (isEpochError || isError || !nymNodes || !epochRewardsData) { + if (isEpochError || isNSApiNodesError || !nsApiNodes || !epochRewardsData) { return ( - + Failed to load node data. @@ -66,17 +72,23 @@ export const NodeDataCard = ({ paramId }: Props) => { // get node info based on wether it's dentity_key or node_id if (paramId.length > 10) { - nodeInfo = nymNodes.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.identity_key === paramId + ); } else { - nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.node_id === Number(paramId) + ); } if (!nodeInfo) return null; - const softwareUpdateTime = format( - new Date(nodeInfo.description.build_information.build_timestamp), - "dd/MM/yyyy", - ); + const softwareUpdateTime = nodeInfo.self_description + ? format( + new Date(nodeInfo.self_description.build_information.build_timestamp), + "dd/MM/yyyy" + ) + : "N/A"; return ( @@ -90,13 +102,21 @@ export const NodeDataCard = ({ paramId }: Props) => { row divider label="Host" - value={nodeInfo.description.host_information.ip_address.toString()} + value={ + nodeInfo.self_description + ? nodeInfo.self_description.host_information.ip_address.toString() + : "N/A" + } /> { - let nodeInfo: IObservatoryNode | undefined; + let nodeInfo: NS_NODE | undefined; const { - data: nymNodes, - isError, - isLoading, + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, @@ -28,16 +28,20 @@ const NodeDelegationsCard = ({ paramId }: Props) => { }); if (paramId.length > 10) { - nodeInfo = nymNodes?.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.identity_key === paramId + ); } else { - nodeInfo = nymNodes?.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.node_id === Number(paramId) + ); } if (!nodeInfo) return null; const id = nodeInfo.node_id; - if (isLoading) { + if (isNSApiNodesLoading) { return ( @@ -48,7 +52,7 @@ const NodeDelegationsCard = ({ paramId }: Props) => { ); } - if (isError) { + if (isNSApiNodesError) { return ( diff --git a/explorer-v2/src/components/nymNodePageComponents/NodePageButtonGroup.tsx b/explorer-v2/src/components/nymNodePageComponents/NodePageButtonGroup.tsx index 7c33f49263..9ec9f4251d 100644 --- a/explorer-v2/src/components/nymNodePageComponents/NodePageButtonGroup.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/NodePageButtonGroup.tsx @@ -1,7 +1,7 @@ "use client"; -import { fetchObservatoryNodes } from "@/app/api"; -import type { IObservatoryNode } from "@/app/api/types"; +import { fetchNSApiNodes } from "@/app/api"; +import type { NS_NODE } from "@/app/api/types"; import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; import { useQuery } from "@tanstack/react-query"; @@ -10,27 +10,32 @@ type Props = { }; export default function NodePageButtonGroup({ paramId }: Props) { - let nodeInfo: IObservatoryNode | undefined; + let nodeInfo: NS_NODE | undefined; - const { data: nymNodes, isError } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + const { data: nsApiNodes = [], isError: isNSApiNodesError } = useQuery({ + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, refetchOnMount: false, }); - if (!nymNodes || isError) return null; + if (!nsApiNodes || isNSApiNodesError) return null; // get node info based on wether it's dentity_key or node_id if (paramId.length > 10) { - nodeInfo = nymNodes.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.identity_key === paramId + ); } else { - nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.node_id === Number(paramId) + ); } + if (!nodeInfo) return null; if (nodeInfo.bonding_address) diff --git a/explorer-v2/src/components/nymNodePageComponents/NodeParametersCard.tsx b/explorer-v2/src/components/nymNodePageComponents/NodeParametersCard.tsx index d73e72ddcd..213a43eb8d 100644 --- a/explorer-v2/src/components/nymNodePageComponents/NodeParametersCard.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/NodeParametersCard.tsx @@ -3,8 +3,8 @@ import { formatBigNum } from "@/utils/formatBigNumbers"; import { Skeleton, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; -import { fetchEpochRewards, fetchObservatoryNodes } from "../../app/api"; -import type { IObservatoryNode, RewardingDetails } from "../../app/api/types"; +import { fetchEpochRewards, fetchNSApiNodes } from "../../app/api"; +import type { NS_NODE } from "../../app/api/types"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; @@ -13,7 +13,7 @@ type Props = { }; export const NodeParametersCard = ({ paramId }: Props) => { - let nodeInfo: IObservatoryNode | undefined; + let nodeInfo: NS_NODE | undefined; // Fetch epoch rewards const { @@ -31,19 +31,19 @@ export const NodeParametersCard = ({ paramId }: Props) => { // Fetch node information const { - data: nymNodes, - isLoading, - isError, + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, refetchOnMount: false, }); - if (isEpochLoading || isLoading) { + if (isEpochLoading || isNSApiNodesLoading) { return ( @@ -54,7 +54,7 @@ export const NodeParametersCard = ({ paramId }: Props) => { ); } - if (isEpochError || isError || !nymNodes || !epochRewardsData) { + if (isEpochError || isNSApiNodesError || !nsApiNodes || !epochRewardsData) { return ( @@ -66,9 +66,13 @@ export const NodeParametersCard = ({ paramId }: Props) => { // get node info based on wether it's dentity_key or node_id if (paramId.length > 10) { - nodeInfo = nymNodes.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.identity_key === paramId + ); } else { - nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.node_id === Number(paramId) + ); } if (!nodeInfo) return null; @@ -77,22 +81,25 @@ export const NodeParametersCard = ({ paramId }: Props) => { const totalStakeFormatted = `${totalStake} NYM`; // Extract reward details - const rewardDetails: RewardingDetails = nodeInfo.rewarding_details; - const profitMarginPercent = - Number(rewardDetails.cost_params.profit_margin_percent) * 100; + const profitMarginPercent = nodeInfo.rewarding_details + ? Number(nodeInfo.rewarding_details.cost_params.profit_margin_percent) * 100 + : 0; const profitMarginPercentFormated = `${profitMarginPercent}%`; - const operatingCosts = - Number(rewardDetails.cost_params.interval_operating_cost.amount) / - 1_000_000; + const operatingCosts = nodeInfo.rewarding_details + ? Number( + nodeInfo.rewarding_details.cost_params.interval_operating_cost.amount + ) / 1_000_000 + : 0; const operatingCostsFormated = `${operatingCosts.toString()} NYM`; const getNodeSaturationPoint = ( - totalStake: number, - stakeSaturationPoint: string, + nodeTotalStake: string, + stakeSaturationPoint: string ): string => { const saturation = Number.parseFloat(stakeSaturationPoint); + const totalStake = Number.parseFloat(nodeTotalStake); if (Number.isNaN(saturation) || saturation <= 0) { throw new Error("Invalid stake saturation point provided"); @@ -105,7 +112,7 @@ export const NodeParametersCard = ({ paramId }: Props) => { const nodeSaturationPoint = getNodeSaturationPoint( nodeInfo.total_stake, - epochRewardsData.interval.stake_saturation_point, + epochRewardsData.interval.stake_saturation_point ); return ( diff --git a/explorer-v2/src/components/nymNodePageComponents/NodeProfileCard.tsx b/explorer-v2/src/components/nymNodePageComponents/NodeProfileCard.tsx index 5bef0c4cf1..18bb793bd6 100644 --- a/explorer-v2/src/components/nymNodePageComponents/NodeProfileCard.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/NodeProfileCard.tsx @@ -1,6 +1,6 @@ "use client"; -import type { IObservatoryNode } from "@/app/api/types"; +import type { NS_NODE } from "@/app/api/types"; import { useChain } from "@cosmos-kit/react"; import { Box, @@ -14,7 +14,7 @@ import { useQuery } from "@tanstack/react-query"; import DOMPurify from "isomorphic-dompurify"; import { useCallback, useState } from "react"; import { RandomAvatar } from "react-random-avatars"; -import { fetchObservatoryNodes } from "../../app/api"; +import { fetchNSApiNodes } from "../../app/api"; import { COSMOS_KIT_USE_CHAIN } from "../../config"; import { useNymClient } from "../../hooks/useNymClient"; import ExplorerCard from "../cards/ExplorerCard"; @@ -31,7 +31,7 @@ type Props = { }; export const NodeProfileCard = ({ paramId }: Props) => { - let nodeInfo: IObservatoryNode | undefined; + let nodeInfo: NS_NODE | undefined; const theme = useTheme(); const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN); @@ -47,12 +47,12 @@ export const NodeProfileCard = ({ paramId }: Props) => { // Fetch node info const { - data: nymNodes, - isLoading: isLoadingNymNodes, - isError, + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, @@ -62,9 +62,13 @@ export const NodeProfileCard = ({ paramId }: Props) => { // get node info based on wether it's dentity_key or node_id if (paramId.length > 10) { - nodeInfo = nymNodes?.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.identity_key === paramId + ); } else { - nodeInfo = nymNodes?.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find( + (node: NS_NODE) => node.node_id === Number(paramId) + ); } const handleOnSelectStake = useCallback(() => { @@ -96,7 +100,7 @@ export const NodeProfileCard = ({ paramId }: Props) => { } }, [isWalletConnected, nodeInfo]); - if (isLoadingNymNodes) { + if (isNSApiNodesLoading) { return ( @@ -105,7 +109,7 @@ export const NodeProfileCard = ({ paramId }: Props) => { ); } - if (isError || !nymNodes) { + if (isNSApiNodesError || !nsApiNodes) { return ( { { nodeId }, fee, "Delegation from Nym Explorer V2", - uNymFunds, + uNymFunds ); setSelectedNodeForStaking(undefined); setInfoModalProps({ @@ -164,12 +168,13 @@ export const NodeProfileCard = ({ paramId }: Props) => { if (!nodeInfo) return null; - const cleanMoniker = DOMPurify.sanitize( - nodeInfo?.self_description.moniker, - ).replace(/&/g, "&"); + const cleanMoniker = DOMPurify.sanitize(nodeInfo.description.moniker).replace( + /&/g, + "&" + ); const cleanDescription = DOMPurify.sanitize( - nodeInfo?.self_description.details, + nodeInfo.description.details ).replace(/&/g, "&"); // get full country name @@ -197,7 +202,7 @@ export const NodeProfileCard = ({ paramId }: Props) => { > {cleanMoniker || "Moniker"} - {nodeInfo.description.auxiliary_details.location && ( + {nodeInfo.geoip.country && ( { diff --git a/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx b/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx index 5c3a2a49bf..866f4e6719 100644 --- a/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx @@ -1,16 +1,16 @@ "use client"; -import { Chip, Skeleton, Stack, Typography } from "@mui/material"; +import { Chip, Skeleton, Stack, Typography, useTheme } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import { fetchEpochRewards, fetchGatewayStatus, - fetchObservatoryNodes, + fetchNSApiNodes, } from "../../app/api"; import type { - IObservatoryNode, LastProbeResult, NodeDescription, + NS_NODE, } from "../../app/api/types"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; @@ -32,7 +32,7 @@ const roleMapping: Record = { }; const getNodeRoles = ( - declaredRoles: NodeDescriptionNotNull["declared_role"], + declaredRoles: NodeDescriptionNotNull["declared_role"] ): RoleString[] => { return Object.entries(declaredRoles) .filter(([, isActive]) => isActive) @@ -81,7 +81,7 @@ function calculateConfigScoreStars(probeResult: LastProbeResult): number { if (as_entry) { const entryScore = [as_entry.can_connect, as_entry.can_route].filter( - Boolean, + Boolean ).length; return entryScore === 2 ? 4 : entryScore === 1 ? 2 : 1; @@ -148,21 +148,23 @@ function calculateWireguardPerformance(probeResult: LastProbeResult): number { } export const NodeRoleCard = ({ paramId }: Props) => { - let nodeInfo: IObservatoryNode | undefined; - + let nodeInfo: NS_NODE | undefined; + const theme = useTheme(); + const isDarkMode = theme.palette.mode === "dark"; // Fetch node info const { - data: nymNodes, - isLoading, - isError, + data: nsApiNodes = [], + isLoading: isNSApiNodesLoading, + isError: isNSApiNodesError, } = useQuery({ - queryKey: ["nymNodes"], - queryFn: fetchObservatoryNodes, + queryKey: ["nsApiNodes"], + queryFn: fetchNSApiNodes, staleTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false, // Prevents unnecessary refetching refetchOnReconnect: false, refetchOnMount: false, }); + const { data: epochRewardsData, isLoading: isEpochLoading, @@ -177,19 +179,19 @@ export const NodeRoleCard = ({ paramId }: Props) => { }); if (paramId.length > 10) { - nodeInfo = nymNodes?.find((node) => node.identity_key === paramId); + nodeInfo = nsApiNodes.find((node) => node.identity_key === paramId); } else { - nodeInfo = nymNodes?.find((node) => node.node_id === Number(paramId)); + nodeInfo = nsApiNodes.find((node) => node.node_id === Number(paramId)); } // Extract node roles once `nodeInfo` is available - const nodeRoles = nodeInfo - ? getNodeRoles(nodeInfo.description.declared_role) + + const nodeRoles = nodeInfo?.self_description + ? getNodeRoles(nodeInfo.self_description.declared_role) : []; // Define whether to fetch gateway status const shouldFetchGatewayStatus = nodeRoles.some((role) => - ["Entry Node", "Exit IPR Node", "Exit NR Node"].includes(role), + ["Entry Node", "Exit IPR Node", "Exit NR Node"].includes(role) ); - // Fetch gateway status only if `shouldFetchGatewayStatus` is true const { data: gatewayStatus } = useQuery({ queryKey: ["gatewayStatus", nodeInfo?.identity_key], @@ -200,7 +202,7 @@ export const NodeRoleCard = ({ paramId }: Props) => { refetchOnReconnect: false, }); - if (isLoading || isEpochLoading) { + if (isNSApiNodesLoading || isEpochLoading) { return ( @@ -210,15 +212,19 @@ export const NodeRoleCard = ({ paramId }: Props) => { ); } - if (isError || !nymNodes || !epochRewardsData || isEpochError) { + if (isNSApiNodesError || !nsApiNodes || !epochRewardsData || isEpochError) { return ( - + Failed to load node data. ); } + if (!nodeInfo) return null; const NodeRoles = nodeRoles.map((role) => ( @@ -226,8 +232,6 @@ export const NodeRoleCard = ({ paramId }: Props) => { )); - if (!nodeInfo) return null; - const qualityOfServiceStars = nodeInfo?.uptime ? calculateQualityOfServiceStars(nodeInfo.uptime) : gatewayStatus @@ -247,9 +251,10 @@ export const NodeRoleCard = ({ paramId }: Props) => { // Function to calculate active set probability const getActiveSetProbability = ( - totalStake: number, - stakeSaturationPoint: string, + nodeTotalStake: string, + stakeSaturationPoint: string ): string => { + const totalStake = Number.parseFloat(nodeTotalStake); const saturation = Number.parseFloat(stakeSaturationPoint); if (Number.isNaN(saturation) || saturation <= 0) { @@ -268,7 +273,7 @@ export const NodeRoleCard = ({ paramId }: Props) => { }; const activeSetProb = getActiveSetProbability( nodeInfo.total_stake, - epochRewardsData.interval.stake_saturation_point, + epochRewardsData.interval.stake_saturation_point ); return (