diff --git a/src/App.css b/src/App.css index c3c5f27..a2030a1 100644 --- a/src/App.css +++ b/src/App.css @@ -158,3 +158,64 @@ canvas { height: 30px; border:none; } + +/* Reality Tunnel — trust threshold slider */ +.header-center { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; +} + +.trust-threshold { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(34, 197, 94, 0.08); + border: 1px solid rgba(34, 197, 94, 0.28); + padding: 5px 10px; + border-radius: 999px; +} + +.trust-slider { + -webkit-appearance: none; + appearance: none; + width: 90px; + height: 4px; + border-radius: 2px; + background: rgba(255,255,255,0.15); + outline: none; + cursor: pointer; +} + +.trust-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: #22c55e; + box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25); + cursor: pointer; + transition: box-shadow 0.15s; +} + +.trust-slider::-webkit-slider-thumb:hover { + box-shadow: 0 0 0 5px rgba(34, 197, 94, 0.35); +} + +.trust-slider::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: #22c55e; + border: none; + cursor: pointer; +} + +.trust-value { + font-size: 11px; + color: #22c55e; + min-width: 28px; + font-variant-numeric: tabular-nums; +} diff --git a/src/App.js b/src/App.js index b5b557a..a8fb520 100644 --- a/src/App.js +++ b/src/App.js @@ -12,6 +12,7 @@ import EndpointSelector from "./EndpointSelector"; function App() { const [endpoint, setEndpoint] = useState("base"); const [userFilterAddress, setUserFilterAddress] = useState(null); + const [trustThreshold, setTrustThreshold] = useState(0); const { address, isConnected } = useAccount(); const [accountLabel, setAccountLabel] = useState(""); @@ -56,8 +57,8 @@ function App() { connectedLabel={accountLabel} />
- - Intuition Testnet + + Intuition Mainnet {({ account, openConnectModal, openAccountModal, mounted }) => { @@ -85,6 +86,8 @@ function App() {
diff --git a/src/GraphVisualization.js b/src/GraphVisualization.js index 2fa45b5..697f2db 100644 --- a/src/GraphVisualization.js +++ b/src/GraphVisualization.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback, useRef } from "react"; +import React, { useEffect, useState, useCallback, useRef, useMemo } from "react"; import { ForceGraph2D, ForceGraph3D } from "react-force-graph"; import SpriteText from "three-spritetext"; import { fetchTriples, fetchTriplesForNode, searchTriples, createClient } from "./api"; @@ -9,8 +9,9 @@ import GraphLegend from "./GraphLegend"; import GraphVR from "./GraphVR"; import NodeDetailsSidebar from "./NodeDetailsSidebar"; import LoadingAnimation from "./LoadingAnimation"; +import { filterGraphByTrust } from "./RealityTunnel"; -const GraphVisualization = ({ endpoint, userFilterAddress }) => { +const GraphVisualization = ({ endpoint, userFilterAddress, trustThreshold = 0 }) => { const [graphData, setGraphData] = useState({ nodes: [], links: [] }); const [initialGraphData, setInitialGraphData] = useState(null); const [isInitialLoad, setIsInitialLoad] = useState(true); @@ -331,6 +332,12 @@ const GraphVisualization = ({ endpoint, userFilterAddress }) => { }, 500); }, []); + // Apply trust threshold filter as a derived view — does not mutate base graphData + const displayGraphData = useMemo( + () => filterGraphByTrust(graphData, trustThreshold), + [graphData, trustThreshold] + ); + // Effect for handling search useEffect(() => { if (shouldSearch) { @@ -475,7 +482,7 @@ const GraphVisualization = ({ endpoint, userFilterAddress }) => { {viewMode === "2D" && ( (fgRef.current = el)} - graphData={graphData} + graphData={displayGraphData} nodeCanvasObject={(node, ctx, globalScale) => { const label = node.label || ""; const fontSize = 12 / globalScale; @@ -548,7 +555,7 @@ const GraphVisualization = ({ endpoint, userFilterAddress }) => { {viewMode === "3D" && ( (fgRef.current = el)} - graphData={graphData} + graphData={displayGraphData} controlType="fly" nodeLabel="label" onNodeClick={handleNodeClick} diff --git a/src/RealityTunnel.js b/src/RealityTunnel.js index a93512d..77a505c 100644 --- a/src/RealityTunnel.js +++ b/src/RealityTunnel.js @@ -7,8 +7,32 @@ const EyeIcon = ({ size = 14 }) => ( ); -// A centered control to pick which address' positions to visualize -export default function RealityTunnel({ value, onChange, connectedAddress, connectedLabel }) { +const TrustIcon = ({ size = 13 }) => ( + + + + +); + +/** + * Reality Tunnel — trust-weighted graph filter. + * + * Props: + * value {string|null} - currently selected address filter + * onChange {fn} - called with address string or null + * connectedAddress {string|null} - wallet address from wagmi + * connectedLabel {string} - human-readable wallet label + * trustThreshold {number} - current trust threshold (0–100, % of max vault shares) + * onTrustThresholdChange {fn} - called with new threshold value (number) + */ +export default function RealityTunnel({ + value, + onChange, + connectedAddress, + connectedLabel, + trustThreshold = 0, + onTrustThresholdChange, +}) { const options = React.useMemo(() => { const list = []; if (connectedAddress) { @@ -23,8 +47,14 @@ export default function RealityTunnel({ value, onChange, connectedAddress, conne return list; }, [connectedAddress, connectedLabel]); + const handleThresholdChange = React.useCallback((e) => { + const val = Number(e.target.value); + if (onTrustThresholdChange) onTrustThresholdChange(val); + }, [onTrustThresholdChange]); + return ( -
+
+ {/* Address selector */}
Reality Tunnel @@ -39,7 +69,75 @@ export default function RealityTunnel({ value, onChange, connectedAddress, conne ))}
+ + {/* Trust Threshold slider */} +
+ + Trust ≥ + + {trustThreshold}% +
); } +/** + * filterGraphByTrust — pure helper for GraphVisualization to consume. + * + * Filters nodes and links so only those with vault share weight >= threshold + * (expressed as a % of the max observed total_shares) survive. + * + * @param {{ nodes: Array, links: Array }} graphData + * @param {number} thresholdPct 0 = show all, 100 = only max-weight edges + * @returns {{ nodes: Array, links: Array }} + */ +export function filterGraphByTrust(graphData, thresholdPct = 0) { + if (!graphData || thresholdPct === 0) return graphData; + + const { nodes, links } = graphData; + + // Find the max vault shares across all nodes + const maxShares = nodes.reduce((max, n) => { + const s = n.vault?.total_shares ?? n.totalShares ?? 0; + return Math.max(max, Number(s)); + }, 0); + + if (maxShares === 0) return graphData; + + const minShares = (thresholdPct / 100) * maxShares; + + // Filter nodes that meet the trust threshold + const acceptedIds = new Set( + nodes + .filter((n) => { + const s = n.vault?.total_shares ?? n.totalShares ?? 0; + return Number(s) >= minShares; + }) + .map((n) => n.id) + ); + + // Filter links where both endpoints survive + const filteredLinks = links.filter( + (l) => acceptedIds.has(l.source?.id ?? l.source) && acceptedIds.has(l.target?.id ?? l.target) + ); + + // Re-filter nodes to only those still referenced by surviving links + const linkedIds = new Set(); + filteredLinks.forEach((l) => { + linkedIds.add(l.source?.id ?? l.source); + linkedIds.add(l.target?.id ?? l.target); + }); + + const filteredNodes = nodes.filter((n) => linkedIds.has(n.id)); + + return { nodes: filteredNodes, links: filteredLinks }; +} diff --git a/src/api.js b/src/api.js index 817e76e..d3d24c7 100644 --- a/src/api.js +++ b/src/api.js @@ -15,8 +15,8 @@ export const ENDPOINTS = { module: BaseSepolia, }, base: { - url: "https://testnet.intuition.sh/v1/graphql", - displayName: "Intuition Testnet", + url: "https://mainnet.intuition.sh/v1/graphql", + displayName: "Intuition Mainnet", module: Base, }, }; diff --git a/src/api/Base.js b/src/api/Base.js index 1506400..6917bff 100644 --- a/src/api/Base.js +++ b/src/api/Base.js @@ -6,8 +6,8 @@ import { export const ENDPOINTS = { base: { - url: "https://testnet.intuition.sh/v1/graphql", - displayName: "Intuition Testnet", + url: "https://mainnet.intuition.sh/v1/graphql", + displayName: "Intuition Mainnet", }, }; // Create GraphQL client based on endpoint