diff --git a/explorer-v2/src/components/nodeTable/NodeTable.tsx b/explorer-v2/src/components/nodeTable/NodeTable.tsx
index 10c96490b0..e7687576bf 100644
--- a/explorer-v2/src/components/nodeTable/NodeTable.tsx
+++ b/explorer-v2/src/components/nodeTable/NodeTable.tsx
@@ -212,7 +212,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
{
id: "name",
header: "",
- size: 190,
+ size: 210,
Header: Node,
accessorKey: "name",
@@ -329,6 +329,40 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
);
},
},
+ {
+ id: "selfBond",
+ header: "Self-bond",
+ accessorKey: "selfBond",
+ Header: Self-bond,
+ Cell: ({ row }) => {
+ const value = row.original.selfBond;
+ let color = "#000000";
+
+ if (value === 0) {
+ color = "#EF4444";
+ }
+
+ return (
+
+ {row.original.selfBond} NYM
+
+ );
+ },
+ },
+ {
+ id: "operatingCosts",
+ header: "Operating costs",
+ accessorKey: "operatingCosts",
+ Header: Operating costs,
+ Cell: ({ row }) => (
+
+ {row.original.operatingCosts} NYM
+
+ ),
+ },
{
id: "profitMarginPercentage",
header: "Profit margin",
diff --git a/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx b/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx
index 4f62dd9a99..22e80b47da 100644
--- a/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx
+++ b/explorer-v2/src/components/nodeTable/NodeTableWithAction.tsx
@@ -9,6 +9,7 @@ import { countryName } from "../../utils/countryName";
import NodeTable from "./NodeTable";
import { useState, useEffect } from "react";
import AdvancedFilters from "./AdvancedFilters";
+import { formatBigNum } from "@/utils/formatBigNumbers";
// Utility function to calculate node saturation point
function getNodeSaturationPoint(
@@ -44,12 +45,24 @@ const mappedNSApiNodes = (
"&"
);
+ const selfBondFormatted = node.rewarding_details?.operator
+ ? formatBigNum(Number(node.rewarding_details.operator) / 1_000_000)
+ : 0;
+
+ const operatingCostsFormatted = node.rewarding_details
+ ? Number(
+ node.rewarding_details.cost_params.interval_operating_cost.amount
+ ) / 1_000_000
+ : 0;
+
return {
name: cleanMoniker,
nodeId: node.node_id,
identity_key: node.identity_key,
countryCode: node.geoip?.country || null,
countryName: countryName(node.geoip?.country || null) || null,
+ selfBond: selfBondFormatted,
+ operatingCosts: operatingCostsFormatted,
profitMarginPercentage: node.rewarding_details
? +node.rewarding_details.cost_params.profit_margin_percent * 100
: 0,
@@ -80,11 +93,60 @@ const NodeTableWithAction = () => {
// All hooks at the top!
const [activeFilter, setActiveFilter] = useState<
"all" | "mixnodes" | "gateways"
- >("all");
- const [uptime, setUptime] = useState<[number, number]>([0, 100]);
+ >(() => {
+ const stored = sessionStorage.getItem("nodeTableActiveFilter");
+ return (stored as "all" | "mixnodes" | "gateways") || "all";
+ });
+ const [uptime, setUptime] = useState<[number, number]>(() => {
+ const stored = sessionStorage.getItem("nodeTableUptime");
+ return stored ? JSON.parse(stored) : [0, 100];
+ });
const [saturation, setSaturation] = useState<[number, number]>([0, 100]);
- const [profitMargin, setProfitMargin] = useState<[number, number]>([0, 100]);
- const [advancedOpen, setAdvancedOpen] = useState(false);
+ const [profitMargin, setProfitMargin] = useState<[number, number]>(() => {
+ const stored = sessionStorage.getItem("nodeTableProfitMargin");
+ return stored ? JSON.parse(stored) : [0, 100];
+ });
+ const [advancedOpen, setAdvancedOpen] = useState(() => {
+ const stored = sessionStorage.getItem("nodeTableAdvancedOpen");
+ return stored ? JSON.parse(stored) : false;
+ });
+
+ // Wrapper functions to handle filter changes and sessionStorage
+ const handleActiveFilterChange = (
+ newFilter: "all" | "mixnodes" | "gateways"
+ ) => {
+ setActiveFilter(newFilter);
+ sessionStorage.setItem("nodeTableActiveFilter", newFilter);
+ };
+
+ const handleUptimeChange = (newUptime: [number, number]) => {
+ setUptime(newUptime);
+ sessionStorage.setItem("nodeTableUptime", JSON.stringify(newUptime));
+ };
+
+ const handleSaturationChange = (newSaturation: [number, number]) => {
+ setSaturation(newSaturation);
+ sessionStorage.setItem(
+ "nodeTableSaturation",
+ JSON.stringify(newSaturation)
+ );
+ };
+
+ const handleProfitMarginChange = (newProfitMargin: [number, number]) => {
+ setProfitMargin(newProfitMargin);
+ sessionStorage.setItem(
+ "nodeTableProfitMargin",
+ JSON.stringify(newProfitMargin)
+ );
+ };
+
+ const handleAdvancedOpenChange = (newAdvancedOpen: boolean) => {
+ setAdvancedOpen(newAdvancedOpen);
+ sessionStorage.setItem(
+ "nodeTableAdvancedOpen",
+ JSON.stringify(newAdvancedOpen)
+ );
+ };
// Use React Query to fetch epoch rewards
const {
@@ -125,9 +187,12 @@ const NodeTableWithAction = () => {
...nsApiNodesData.map((n) => n.stakeSaturation || 0)
);
- // Ensure saturation filter always covers the full range when maxSaturation changes, but only after data is loaded
+ // Initialize saturation from sessionStorage or set to maxSaturation when data is loaded
useEffect(() => {
- if (nsApiNodesData.length > 0) {
+ const stored = sessionStorage.getItem("nodeTableSaturation");
+ if (stored) {
+ setSaturation(JSON.parse(stored));
+ } else if (nsApiNodesData.length > 0) {
setSaturation([0, maxSaturation]);
}
}, [maxSaturation, nsApiNodesData.length]);
@@ -209,16 +274,16 @@ const NodeTableWithAction = () => {
diff --git a/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx b/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx
index 7cac5cd0fb..6b94de7118 100644
--- a/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx
+++ b/explorer-v2/src/components/nymNodePageComponents/NodeRoleCard.tsx
@@ -226,7 +226,6 @@ export const NodeRoleCard = ({ paramId }: Props) => {
}
if (!nodeInfo) return null;
- console.log("nodeInfo", nodeInfo);
const NodeRoles = nodeRoles.map((role) => (