diff --git a/CLAUDE.md b/CLAUDE.md index 270d8a83..6d75c9e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ OpenScan is a trustless, open-source, standalone web-app and multi-chain blockch ```bash npm start -# Runs on http://localhost:3000 +# Runs on http://localhost:3030 ``` ### Build for Production @@ -89,11 +89,11 @@ OpenScan follows a layered architecture with clear separation between data fetch - Returns `DataWithMetadata` when using parallel strategy, containing: - `data`: The primary result (from first successful provider) - `metadata`: Optional RPCMetadata with all provider responses, hashes, and inconsistency flags - - 30-second in-memory cache keyed by `chainId:type:identifier` + - 30-second in-memory cache keyed by `networkId:type:identifier` - Supports trace operations (debug_traceTransaction, etc.) for localhost networks only 5. **Hook Layer** (`hooks/`) - React integration - - `useDataService(chainId)`: Creates DataService instance with strategy from settings + - `useDataService(networkId)`: Creates DataService instance with strategy from settings - `useProviderSelection`: Manages user's selected RPC provider in parallel mode - `useSelectedData`: Extracts data from specific provider based on user selection @@ -184,6 +184,7 @@ OpenScan includes special support for localhost development: - Use `npm run lint:fix` to auto-fix linting issues (max 1024 diagnostics shown) - **TypeScript** with strict mode (`noImplicitAny`, `noImplicitReturns`, `noUncheckedIndexedAccess`) - **React 19** with functional components and hooks +- **CSS** All styles should be on `src/styles` folder, avoid using in line component styles. ## Coding Standards and Workflow @@ -205,6 +206,11 @@ npm run typecheck npm run test:run ``` +### Commits + +- Follow the convetional commit standard v1.0.0 +- Commit without claude attribution + ### Code Quality Requirements - All code must pass Biome formatting and linting checks @@ -212,6 +218,7 @@ npm run test:run - Follow the 100-character line width limit - Use 2-space indentation consistently - Adhere to Biome's recommended linting rules +- Keep documentation up to date. ### When Claude Code Modifies Files @@ -238,9 +245,9 @@ npm run test:run ### When Working with Cache -- Cache keys format: `${chainId}:${type}:${identifier}` +- Cache keys format: `${networkId}:${type}:${identifier}` - Don't cache "latest" block queries -- Clear cache when switching networks using `clearCacheForChain(chainId)` +- Clear cache when switching networks using `clearCacheForChain(networkId)` - Default timeout: 30 seconds ### RPC Strategy Switching diff --git a/README.md b/README.md index 0e8c836e..2870b1b8 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ npm install npm start ``` -The app will open at `http://localhost:3000` +The app will open at `http://localhost:3030` ### Use with Anvil/Foundry diff --git a/src/App.tsx b/src/App.tsx index 2a358a1d..3b9abb1d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,11 @@ -import { darkTheme, lightTheme, RainbowKitProvider } from "@rainbow-me/rainbowkit"; +import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import { HashRouter, Navigate, Route, BrowserRouter as Router, Routes } from "react-router-dom"; import { WagmiProvider } from "wagmi"; +import { cssVariables, getRainbowKitTheme } from "./theme"; import { networkConfig } from "./utils/networkConfig"; -import "./styles/rainbowkit.css"; +import "@rainbow-me/rainbowkit/styles.css"; // Detect if we're running on GitHub Pages and get the correct basename function getBasename(): string { @@ -28,6 +29,7 @@ import Footer from "./components/common/Footer"; import { IsometricBlocks } from "./components/common/IsometricBlocks"; import NotificationDisplay from "./components/common/NotificationDisplay"; import Navbar from "./components/navbar"; +import "./styles/base.css"; import "./styles/styles.css"; import "./styles/layouts.css"; import "./styles/components.css"; @@ -148,11 +150,22 @@ function App() { function RainbowKitProviderWrapper({ children }: { children: React.ReactNode }) { const { isDarkMode } = useTheme(); - return ( - - {children} - - ); + // Inject CSS variables and set data-theme attribute + useEffect(() => { + // Inject CSS variables if not already present + const styleId = "openscan-theme-variables"; + if (!document.getElementById(styleId)) { + const style = document.createElement("style"); + style.id = styleId; + style.textContent = cssVariables; + document.head.appendChild(style); + } + + // Set data-theme attribute for CSS variable switching + document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light"); + }, [isDarkMode]); + + return {children}; } export default App; diff --git a/src/components/common/ExtraDataDisplay.tsx b/src/components/common/ExtraDataDisplay.tsx new file mode 100644 index 00000000..2316ee0d --- /dev/null +++ b/src/components/common/ExtraDataDisplay.tsx @@ -0,0 +1,74 @@ +import type React from "react"; +import { useMemo, useState } from "react"; + +interface ExtraDataDisplayProps { + hexData: string; + showToggle?: boolean; +} + +const ExtraDataDisplay: React.FC = ({ hexData, showToggle = true }) => { + const [showRaw, setShowRaw] = useState(false); + + const decoded = useMemo(() => { + if (!hexData || hexData === "0x") return null; + + try { + // Remove 0x prefix + const hex = hexData.startsWith("0x") ? hexData.slice(2) : hexData; + + // Convert hex to bytes + const bytes: number[] = []; + for (let i = 0; i < hex.length; i += 2) { + bytes.push(Number.parseInt(hex.substr(i, 2), 16)); + } + + // Check if all bytes are printable ASCII (32-126) + const isPrintable = bytes.every((b) => b >= 32 && b <= 126); + + if (isPrintable && bytes.length > 0) { + return String.fromCharCode(...bytes); + } + + // Try UTF-8 decoding + const decoder = new TextDecoder("utf-8", { fatal: true }); + const uint8Array = new Uint8Array(bytes); + const decoded = decoder.decode(uint8Array); + + // Check if result contains mostly printable characters + const printableCount = decoded.split("").filter((c) => c.charCodeAt(0) >= 32).length; + if (printableCount / decoded.length > 0.8) { + return decoded; + } + + return null; + } catch { + return null; + } + }, [hexData]); + + const hasDecodedValue = decoded !== null && decoded.length > 0; + const displayValue = showRaw || !hasDecodedValue ? hexData : decoded; + + return ( +
+ + {hasDecodedValue && !showRaw && ( + "{displayValue}" + )} + {(!hasDecodedValue || showRaw) && {displayValue}} + + {showToggle && hasDecodedValue && ( + + )} +
+ ); +}; + +export default ExtraDataDisplay; diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx index 85516f60..d9da6851 100644 --- a/src/components/common/Footer.tsx +++ b/src/components/common/Footer.tsx @@ -40,15 +40,23 @@ const Footer: React.FC = ({ className = "" }) => {