Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 5 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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("");

Expand Down Expand Up @@ -56,8 +57,8 @@ function App() {
connectedLabel={accountLabel}
/>
<div className="header-right">
<span className="env-badge" title="Using Intuition Testnet API">
<span className="env-dot" /> Intuition Testnet
<span className="env-badge" title="Using Intuition Mainnet API">
<span className="env-dot" style={{ background: '#22c55e' }} /> Intuition Mainnet
</span>
<ConnectButton.Custom>
{({ account, openConnectModal, openAccountModal, mounted }) => {
Expand Down Expand Up @@ -85,6 +86,8 @@ function App() {
<GraphVisualization
endpoint={endpoint}
userFilterAddress={userFilterAddress}
trustThreshold={trustThreshold}
onTrustThresholdChange={setTrustThreshold}
/>
</main>
</div>
Expand Down
15 changes: 11 additions & 4 deletions src/GraphVisualization.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -475,7 +482,7 @@ const GraphVisualization = ({ endpoint, userFilterAddress }) => {
{viewMode === "2D" && (
<ForceGraph2D
ref={(el) => (fgRef.current = el)}
graphData={graphData}
graphData={displayGraphData}
nodeCanvasObject={(node, ctx, globalScale) => {
const label = node.label || "";
const fontSize = 12 / globalScale;
Expand Down Expand Up @@ -548,7 +555,7 @@ const GraphVisualization = ({ endpoint, userFilterAddress }) => {
{viewMode === "3D" && (
<ForceGraph3D
ref={(el) => (fgRef.current = el)}
graphData={graphData}
graphData={displayGraphData}
controlType="fly"
nodeLabel="label"
onNodeClick={handleNodeClick}
Expand Down
104 changes: 101 additions & 3 deletions src/RealityTunnel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,32 @@ const EyeIcon = ({ size = 14 }) => (
</svg>
);

// A centered control to pick which address' positions to visualize
export default function RealityTunnel({ value, onChange, connectedAddress, connectedLabel }) {
const TrustIcon = ({ size = 13 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L3 7v5c0 5.25 3.75 10.15 9 11.35C17.25 22.15 21 17.25 21 12V7L12 2Z" fill="none" stroke="#22c55e" strokeWidth="2"/>
<path d="M9 12l2 2 4-4" stroke="#22c55e" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);

/**
* 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) {
Expand All @@ -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 (
<div className="header-center" title="Filter graph by address positions">
<div className="header-center" title="Filter graph by address positions and trust weight">
{/* Address selector */}
<div className="tunnel">
<EyeIcon />
<span className="tunnel-label">Reality Tunnel</span>
Expand All @@ -39,7 +69,75 @@ export default function RealityTunnel({ value, onChange, connectedAddress, conne
))}
</select>
</div>

{/* Trust Threshold slider */}
<div className="trust-threshold" title="Filter edges by minimum $TRUST staked (vault shares)">
<TrustIcon />
<span className="tunnel-label">Trust&nbsp;β‰₯</span>
<input
type="range"
className="trust-slider"
min={0}
max={100}
step={1}
value={trustThreshold}
onChange={handleThresholdChange}
aria-label="Minimum trust threshold"
/>
<span className="trust-value">{trustThreshold}%</span>
</div>
</div>
);
}

/**
* 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 };
}
4 changes: 2 additions & 2 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down
4 changes: 2 additions & 2 deletions src/api/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down