diff --git a/.claude/rules/patterns.md b/.claude/rules/patterns.md index f35bf62..8d9e0d1 100644 --- a/.claude/rules/patterns.md +++ b/.claude/rules/patterns.md @@ -58,3 +58,37 @@ OpenScan includes special support for localhost development: - Use `--overlay-light-*` variables for backgrounds that need to work in both themes - Use `--text-primary`, `--text-secondary`, `--text-tertiary` for text colors - Never hardcode `rgba(255, 255, 255, X)` - use CSS variables instead + +## Logger Utility + +Always use the logger utility instead of `console.*` methods for runtime logging. + +### Usage + +```typescript +import { logger } from "../utils/logger"; + +logger.debug("Debug info for development"); +logger.info("General information"); +logger.warn("Warning message"); +logger.error("Error message", error); +``` + +### Log Level Guidelines + +- **debug**: Development-only information (verbose data, state dumps, request/response details) +- **info**: General operational information (feature activity, successful operations) +- **warn**: Potential issues that don't break functionality (fallbacks, deprecations, recoverable errors) +- **error**: Actual errors that affect functionality (failed operations, exceptions) + +### Environment Filtering + +| Environment | Visible Levels | +|-------------|--------------------------| +| development | debug, info, warn, error | +| staging | info, warn, error | +| production | warn, error | + +### Build-time Safety + +In production builds, `console.log` calls are stripped via Vite's terser `pure_funcs` option as a safety net for any missed migrations. diff --git a/src/components/LazyComponents.tsx b/src/components/LazyComponents.tsx index 66f4e13..b65810a 100644 --- a/src/components/LazyComponents.tsx +++ b/src/components/LazyComponents.tsx @@ -1,4 +1,5 @@ import { lazy, Suspense } from "react"; +import { logger } from "../utils/logger"; import Loading from "./common/Loading"; // Lazy load page components - Shared @@ -77,7 +78,7 @@ export const LazyGasTracker = withSuspense(GasTracker); * This ensures navigation between pages is instant (no chunk download delay). */ export function preloadAllRoutes() { - console.log("Preloading all route chunks..."); + logger.debug("Preloading all route chunks..."); // Shared pages import("./pages/home"); import("./pages/settings"); diff --git a/src/components/common/ErrorBoundary.tsx b/src/components/common/ErrorBoundary.tsx index 93371c3..3bb6382 100644 --- a/src/components/common/ErrorBoundary.tsx +++ b/src/components/common/ErrorBoundary.tsx @@ -1,5 +1,6 @@ import i18next from "i18next"; import React, { Component, type ErrorInfo, type ReactNode } from "react"; +import { logger } from "../../utils/logger"; interface Props { children: ReactNode; @@ -25,7 +26,7 @@ export class ErrorBoundary extends Component { componentDidCatch(error: Error, errorInfo: ErrorInfo) { // Log error details - console.error("ErrorBoundary caught an error:", error, errorInfo); + logger.error("ErrorBoundary caught an error:", error, errorInfo); this.setState({ error, @@ -107,7 +108,7 @@ export const useErrorHandler = () => { }, []); const captureError = React.useCallback((error: Error) => { - console.error("Error captured:", error); + logger.error("Error captured:", error); setError(error); }, []); diff --git a/src/components/navbar/NetworkBlockIndicator.tsx b/src/components/navbar/NetworkBlockIndicator.tsx index 9448ed3..a149279 100644 --- a/src/components/navbar/NetworkBlockIndicator.tsx +++ b/src/components/navbar/NetworkBlockIndicator.tsx @@ -5,6 +5,7 @@ import { AppContext, useNetworks } from "../../context/AppContext"; import { RpcClient } from "@openscan/network-connectors"; import { useDataService } from "../../hooks/useDataService"; import { formatGasPrice } from "../../utils/formatUtils"; +import { logger } from "../../utils/logger"; import { resolveNetwork, getNetworkRpcKey } from "../../utils/networkResolver"; interface NetworkBlockIndicatorProps { @@ -74,12 +75,12 @@ export function NetworkBlockIndicator({ className }: NetworkBlockIndicatorProps) setGasPrice(gasPricesResult.data.average); } } catch (error) { - console.error("Failed to fetch gas price:", error); + logger.error("Failed to fetch gas price:", error); } } } } catch (error) { - console.error("Failed to fetch block number:", error); + logger.error("Failed to fetch block number:", error); if (isMounted) { setIsLoading(false); } diff --git a/src/components/pages/about/index.tsx b/src/components/pages/about/index.tsx index 3e05497..26b6856 100644 --- a/src/components/pages/about/index.tsx +++ b/src/components/pages/about/index.tsx @@ -2,6 +2,7 @@ import type React from "react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { getEnabledNetworks } from "../../../config/networks"; +import { logger } from "../../../utils/logger"; import NetworkIcon from "../../common/NetworkIcon"; interface GitHubStats { @@ -54,7 +55,7 @@ const About: React.FC = () => { } } } catch (error) { - console.error("Failed to fetch GitHub stats:", error); + logger.error("Failed to fetch GitHub stats:", error); } }; diff --git a/src/components/pages/bitcoin/BitcoinBlocksPage.tsx b/src/components/pages/bitcoin/BitcoinBlocksPage.tsx index a6a0091..3eb14a7 100644 --- a/src/components/pages/bitcoin/BitcoinBlocksPage.tsx +++ b/src/components/pages/bitcoin/BitcoinBlocksPage.tsx @@ -8,6 +8,7 @@ import { formatTimestamp, truncateBlockHash, } from "../../../utils/bitcoinFormatters"; +import { logger } from "../../../utils/logger"; import Loader from "../../common/Loader"; export default function BitcoinBlocksPage() { @@ -65,7 +66,7 @@ export default function BitcoinBlocksPage() { setBlocks(fetchedBlocks); } catch (err) { - console.error("Error fetching Bitcoin blocks:", err); + logger.error("Error fetching Bitcoin blocks:", err); setError(err instanceof Error ? err.message : "Failed to fetch blocks"); } finally { setLoading(false); diff --git a/src/components/pages/bitcoin/BitcoinMempoolPage.tsx b/src/components/pages/bitcoin/BitcoinMempoolPage.tsx index d5fc8c2..de0e66d 100644 --- a/src/components/pages/bitcoin/BitcoinMempoolPage.tsx +++ b/src/components/pages/bitcoin/BitcoinMempoolPage.tsx @@ -4,6 +4,7 @@ import { useDataService } from "../../../hooks/useDataService"; import type { BitcoinNetworkStats, BitcoinTransaction } from "../../../types"; import { formatBTC, formatTimeAgo, truncateHash } from "../../../utils/bitcoinFormatters"; import { calculateTotalOutput } from "../../../utils/bitcoinUtils"; +import { logger } from "../../../utils/logger"; import Loader from "../../common/Loader"; const PAGE_SIZE = 100; @@ -48,7 +49,7 @@ export default function BitcoinMempoolPage() { setTotalCount(mempoolResult.total); setError(null); } catch (err) { - console.error("Error fetching mempool data:", err); + logger.error("Error fetching mempool data:", err); setError(err instanceof Error ? err.message : "Failed to fetch mempool data"); } finally { setLoading(false); diff --git a/src/components/pages/bitcoin/BitcoinTransactionsPage.tsx b/src/components/pages/bitcoin/BitcoinTransactionsPage.tsx index 6f18f9a..720e68e 100644 --- a/src/components/pages/bitcoin/BitcoinTransactionsPage.tsx +++ b/src/components/pages/bitcoin/BitcoinTransactionsPage.tsx @@ -10,6 +10,7 @@ import { truncateHash, } from "../../../utils/bitcoinFormatters"; import { calculateTotalOutput } from "../../../utils/bitcoinUtils"; +import { logger } from "../../../utils/logger"; import Loader from "../../common/Loader"; export default function BitcoinTransactionsPage() { @@ -40,7 +41,7 @@ export default function BitcoinTransactionsPage() { const txs = await adapter.getLatestTransactions(TXS_PER_PAGE); setTransactions(txs); } catch (err) { - console.error("Error fetching Bitcoin transactions:", err); + logger.error("Error fetching Bitcoin transactions:", err); setError(err instanceof Error ? err.message : "Failed to fetch transactions"); } finally { setLoading(false); diff --git a/src/components/pages/evm/address/AddressDetails.tsx b/src/components/pages/evm/address/AddressDetails.tsx index 3784af6..97e9acd 100644 --- a/src/components/pages/evm/address/AddressDetails.tsx +++ b/src/components/pages/evm/address/AddressDetails.tsx @@ -5,6 +5,7 @@ import { encodeFunctionData, parseEther, toFunctionSelector } from "viem"; import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import { AppContext } from "../../../../context"; import { useSourcify } from "../../../../hooks/useSourcify"; +import { logger } from "../../../../utils/logger"; import type { ABI, ABIParameter, @@ -242,7 +243,7 @@ const AddressDisplay: React.FC = React.memo( value: txValue, }); } catch (err) { - console.error("Error writing to contract:", err); + logger.error("Error writing to contract:", err); alert(`Error: ${err instanceof Error ? err.message : "Unknown error"}`); } }, [selectedWriteFunction, functionInputs, addressHash, contractData?.abi, writeContract]); @@ -320,7 +321,7 @@ const AddressDisplay: React.FC = React.memo( setReadFunctionResult(data.result); } catch (err) { - console.error("Error reading from contract:", err); + logger.error("Error reading from contract:", err); setReadFunctionResult(`Error: ${err instanceof Error ? err.message : "Unknown error"}`); } finally { setIsReadingFunction(false); diff --git a/src/components/pages/evm/address/displays/ERC1155Display.tsx b/src/components/pages/evm/address/displays/ERC1155Display.tsx index 5553c4d..565bf97 100644 --- a/src/components/pages/evm/address/displays/ERC1155Display.tsx +++ b/src/components/pages/evm/address/displays/ERC1155Display.tsx @@ -9,6 +9,7 @@ import { } from "../../../../../services/MetadataService"; import type { Address, ENSReverseResult, RPCMetadata } from "../../../../../types"; import { decodeAbiString } from "../../../../../utils/hexUtils"; +import { logger } from "../../../../../utils/logger"; import { AddressHeader } from "../shared"; import ContractInfoCard from "../shared/ContractInfoCard"; import ContractInfoCards from "../shared/ContractInfoCards"; @@ -55,7 +56,9 @@ const ERC1155Display: React.FC = ({ // Fetch token metadata from explorer-metadata useEffect(() => { - fetchToken(Number(networkId), addressHash).then(setTokenMetadata).catch(console.error); + fetchToken(Number(networkId), addressHash) + .then(setTokenMetadata) + .catch((err) => logger.error("Failed to fetch token metadata:", err)); }, [networkId, addressHash]); // Fetch on-chain ERC1155 data diff --git a/src/components/pages/evm/address/displays/ERC20Display.tsx b/src/components/pages/evm/address/displays/ERC20Display.tsx index 9088c03..6b2a8d0 100644 --- a/src/components/pages/evm/address/displays/ERC20Display.tsx +++ b/src/components/pages/evm/address/displays/ERC20Display.tsx @@ -9,6 +9,7 @@ import { } from "../../../../../services/MetadataService"; import type { Address, ENSReverseResult, RPCMetadata } from "../../../../../types"; import { hexToUtf8 } from "../../../../../utils/erc20Utils"; +import { logger } from "../../../../../utils/logger"; import { AddressHeader } from "../shared"; import ContractInfoCard from "../shared/ContractInfoCard"; import ContractInfoCards from "../shared/ContractInfoCards"; @@ -56,7 +57,9 @@ const ERC20Display: React.FC = ({ // Fetch token metadata from explorer-metadata useEffect(() => { - fetchToken(Number(networkId), addressHash).then(setTokenMetadata).catch(console.error); + fetchToken(Number(networkId), addressHash) + .then(setTokenMetadata) + .catch((err) => logger.error("Failed to fetch token metadata:", err)); }, [networkId, addressHash]); // Fetch on-chain token data diff --git a/src/components/pages/evm/address/displays/ERC721Display.tsx b/src/components/pages/evm/address/displays/ERC721Display.tsx index 956298d..623cca7 100644 --- a/src/components/pages/evm/address/displays/ERC721Display.tsx +++ b/src/components/pages/evm/address/displays/ERC721Display.tsx @@ -9,6 +9,7 @@ import { } from "../../../../../services/MetadataService"; import type { Address, ENSReverseResult, RPCMetadata } from "../../../../../types"; import { decodeAbiString } from "../../../../../utils/hexUtils"; +import { logger } from "../../../../../utils/logger"; import { AddressHeader } from "../shared"; import ContractInfoCard from "../shared/ContractInfoCard"; import ContractInfoCards from "../shared/ContractInfoCards"; @@ -55,7 +56,9 @@ const ERC721Display: React.FC = ({ // Fetch token metadata from explorer-metadata useEffect(() => { - fetchToken(Number(networkId), addressHash).then(setTokenMetadata).catch(console.error); + fetchToken(Number(networkId), addressHash) + .then(setTokenMetadata) + .catch((err) => logger.error("Failed to fetch token metadata:", err)); }, [networkId, addressHash]); // Fetch on-chain NFT data diff --git a/src/components/pages/evm/address/shared/AccountInfoCards.tsx b/src/components/pages/evm/address/shared/AccountInfoCards.tsx index ab7e72e..61e19f6 100644 --- a/src/components/pages/evm/address/shared/AccountInfoCards.tsx +++ b/src/components/pages/evm/address/shared/AccountInfoCards.tsx @@ -3,6 +3,7 @@ import { useContext, useEffect, useState } from "react"; import { AppContext } from "../../../../../context"; import { getNativeTokenPrice } from "../../../../../services/PriceService"; import type { Address, ENSReverseResult } from "../../../../../types"; +import { logger } from "../../../../../utils/logger"; import AccountMoreInfoCard from "./AccountMoreInfoCard"; import AccountOverviewCard from "./AccountOverviewCard"; @@ -48,7 +49,7 @@ const AccountInfoCards: React.FC = ({ const price = await getNativeTokenPrice(networkId, rpcUrl, mainnetRpcUrl); setNativeTokenPrice(price); } catch (err) { - console.error("Error fetching native token price:", err); + logger.error("Error fetching native token price:", err); setNativeTokenPrice(null); } finally { setPriceLoading(false); diff --git a/src/components/pages/evm/address/shared/ContractInfoCards.tsx b/src/components/pages/evm/address/shared/ContractInfoCards.tsx index 2437bf6..5a4c600 100644 --- a/src/components/pages/evm/address/shared/ContractInfoCards.tsx +++ b/src/components/pages/evm/address/shared/ContractInfoCards.tsx @@ -3,6 +3,7 @@ import { useContext, useEffect, useState } from "react"; import { AppContext } from "../../../../../context"; import { getNativeTokenPrice } from "../../../../../services/PriceService"; import type { Address, ENSReverseResult } from "../../../../../types"; +import { logger } from "../../../../../utils/logger"; import AccountOverviewCard from "./AccountOverviewCard"; import ContractMoreInfoCard from "./ContractMoreInfoCard"; @@ -48,7 +49,7 @@ const ContractInfoCards: React.FC = ({ const price = await getNativeTokenPrice(networkId, rpcUrl, mainnetRpcUrl); setNativeTokenPrice(price); } catch (err) { - console.error("Error fetching native token price:", err); + logger.error("Error fetching native token price:", err); setNativeTokenPrice(null); } finally { setPriceLoading(false); diff --git a/src/components/pages/evm/address/shared/ContractInteraction.tsx b/src/components/pages/evm/address/shared/ContractInteraction.tsx index 42eb925..69c763c 100644 --- a/src/components/pages/evm/address/shared/ContractInteraction.tsx +++ b/src/components/pages/evm/address/shared/ContractInteraction.tsx @@ -1,12 +1,13 @@ import { ConnectButton } from "@rainbow-me/rainbowkit"; import type React from "react"; import { useCallback, useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { encodeFunctionData, parseEther } from "viem"; import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import { AppContext } from "../../../../../context"; import type { ABI, ABIParameter, EventABI, FunctionABI } from "../../../../../types"; -import { useTranslation } from "react-i18next"; +import { logger } from "../../../../../utils/logger"; /** * Generate a unique key for a function based on its name and input types @@ -153,7 +154,7 @@ const ContractInteraction: React.FC = ({ value: txValue, }); } catch (err) { - console.error("Error writing to contract:", err); + logger.error("Error writing to contract:", err); alert(`Error: ${err instanceof Error ? err.message : "Unknown error"}`); } }, [selectedWriteFunction, functionInputs, addressHash, abi, writeContract]); @@ -224,7 +225,7 @@ const ContractInteraction: React.FC = ({ setReadFunctionResult(data.result); } catch (err) { - console.error("Error reading from contract:", err); + logger.error("Error reading from contract:", err); setReadFunctionResult(`Error: ${err instanceof Error ? err.message : "Unknown error"}`); } finally { setIsReadingFunction(false); diff --git a/src/components/pages/evm/address/shared/CustomTokenModal.tsx b/src/components/pages/evm/address/shared/CustomTokenModal.tsx index a691e07..590e6fc 100644 --- a/src/components/pages/evm/address/shared/CustomTokenModal.tsx +++ b/src/components/pages/evm/address/shared/CustomTokenModal.tsx @@ -1,8 +1,9 @@ import type React from "react"; import { useCallback, useContext, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { AppContext } from "../../../../../context"; import { fetchToken, getAssetUrl } from "../../../../../services/MetadataService"; -import { useTranslation } from "react-i18next"; +import { logger } from "../../../../../utils/logger"; import { fetchERC20TokenInfo, formatTokenBalance, @@ -86,7 +87,7 @@ const CustomTokenModal: React.FC = ({ setTokenInfo(info); } } catch (err) { - console.error("Error fetching token info:", err); + logger.error("Error fetching token info:", err); setError("Failed to fetch token information"); setTokenInfo(null); } finally { diff --git a/src/components/pages/evm/address/shared/TokenHoldings.tsx b/src/components/pages/evm/address/shared/TokenHoldings.tsx index 98a7aa6..d164544 100644 --- a/src/components/pages/evm/address/shared/TokenHoldings.tsx +++ b/src/components/pages/evm/address/shared/TokenHoldings.tsx @@ -2,6 +2,7 @@ import type React from "react"; import { useCallback, useContext, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { AppContext } from "../../../../../context"; +import { logger } from "../../../../../utils/logger"; interface TokenLogoProps { src?: string; @@ -176,7 +177,7 @@ const TokenHoldings: React.FC = ({ const tokenHoldings = await fetchSupporterTokenBalances(tokens, rpcUrl); setHoldings(tokenHoldings); } catch (err) { - console.error("Error fetching supporter tokens:", err); + logger.error("Error fetching supporter tokens:", err); setError("Failed to fetch token holdings"); } finally { setLoading(false); @@ -231,7 +232,7 @@ const TokenHoldings: React.FC = ({ setPopularLoaded(true); } catch (err) { - console.error("Error fetching popular tokens:", err); + logger.error("Error fetching popular tokens:", err); setError("Failed to fetch popular tokens"); } finally { setPopularLoading(false); diff --git a/src/components/pages/evm/blocks/index.tsx b/src/components/pages/evm/blocks/index.tsx index 70ee189..8ba9b5a 100644 --- a/src/components/pages/evm/blocks/index.tsx +++ b/src/components/pages/evm/blocks/index.tsx @@ -5,6 +5,7 @@ import { RPCIndicator } from "../../../common/RPCIndicator"; import { useDataService } from "../../../../hooks/useDataService"; import { useProviderSelection } from "../../../../hooks/useProviderSelection"; import type { Block, DataWithMetadata } from "../../../../types"; +import { logger } from "../../../../utils/logger"; import Loader from "../../../common/Loader"; const BLOCKS_PER_PAGE = 10; @@ -89,12 +90,12 @@ export default function Blocks() { blockNumbers.map((num) => dataService.networkAdapter.getBlock(num)), ); - console.log("Fetched blocks:", blockResults); + logger.debug("Fetched blocks:", blockResults); // Store complete results with metadata setBlocksResult(blockResults); // biome-ignore lint/suspicious/noExplicitAny: } catch (err: any) { - console.error("Error fetching blocks:", err); + logger.error("Error fetching blocks:", err); setError(err.message || t("errors.failedToFetchBlocks")); } finally { setLoading(false); diff --git a/src/components/pages/evm/gastracker/index.tsx b/src/components/pages/evm/gastracker/index.tsx index 220f336..7da8ae2 100644 --- a/src/components/pages/evm/gastracker/index.tsx +++ b/src/components/pages/evm/gastracker/index.tsx @@ -1,4 +1,5 @@ import { useCallback, useContext, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; import { AppContext, useNetwork } from "../../../../context/AppContext"; import { useDataService } from "../../../../hooks/useDataService"; @@ -11,14 +12,42 @@ const REFRESH_INTERVAL = 15000; // 15 seconds // Common transaction gas estimates const TX_GAS_ESTIMATES = [ - { name: "ETH Transfer", gas: 21000, description: "Native token transfer" }, - { name: "ERC20 Transfer", gas: 65000, description: "Token transfer" }, - { name: "ERC20 Approve", gas: 46000, description: "Token approval" }, - { name: "NFT Transfer", gas: 85000, description: "ERC721 transfer" }, - { name: "Swap", gas: 150000, description: "DEX swap" }, - { name: "NFT Mint", gas: 120000, description: "Mint an NFT" }, - { name: "Contract Deploy (Simple)", gas: 300000, description: "Deploy basic contract" }, -]; + { + nameKey: "gasTracker.ethTransfer", + descriptionKey: "gasTracker.ethTransferDesc", + gas: 21000, + }, + { + nameKey: "gasTracker.erc20Transfer", + descriptionKey: "gasTracker.erc20TransferDesc", + gas: 65000, + }, + { + nameKey: "gasTracker.erc20Approve", + descriptionKey: "gasTracker.erc20ApproveDesc", + gas: 46000, + }, + { + nameKey: "gasTracker.nftTransfer", + descriptionKey: "gasTracker.nftTransferDesc", + gas: 85000, + }, + { + nameKey: "gasTracker.swap", + descriptionKey: "gasTracker.swapDesc", + gas: 150000, + }, + { + nameKey: "gasTracker.nftMint", + descriptionKey: "gasTracker.nftMintDesc", + gas: 120000, + }, + { + nameKey: "gasTracker.contractDeploy", + descriptionKey: "gasTracker.contractDeployDesc", + gas: 300000, + }, +] as const; interface GasTrackerData { gasPrices: GasPrices | null; @@ -65,6 +94,7 @@ function calculateTxCost( } export default function GasTracker() { + const { t } = useTranslation("network"); const { networkId } = useParams<{ networkId?: string }>(); const numericNetworkId = Number(networkId) || 1; const networkConfig = useNetwork(numericNetworkId); @@ -129,19 +159,17 @@ export default function GasTracker() {
-

Gas Tracker

+

{t("gasTracker.title")}

- {loading && !gasPrices && } + {loading && !gasPrices && } - {error &&

Error: {error}

} + {error &&

{t("gasTracker.errorLoadingGas", { error })}

} {gasPrices && ( <>
-

- Based on the last 20 blocks. Prices include base fee + priority fee. -

+

{t("gasTracker.basedOnLastBlocks")}

{(() => { const lowPrice = formatGasPrice(gasPrices.low); @@ -153,32 +181,38 @@ export default function GasTracker() { <>
-
Low
+
{t("gasTracker.low")}
{lowPrice.value} {lowPrice.unit}
-
~5+ min
+
+ {t("gasTracker.estimatedTimeLow")} +
-
Average
+
{t("gasTracker.average")}
{avgPrice.value} {avgPrice.unit}
-
~1-3 min
+
+ {t("gasTracker.estimatedTimeAvg")} +
-
High
+
{t("gasTracker.high")}
{highPrice.value} {highPrice.unit}
-
~30 sec
+
+ {t("gasTracker.estimatedTimeHigh")} +
- Base Fee: + {t("gasTracker.baseFee")} {baseFee.value} {baseFee.unit} @@ -189,19 +223,21 @@ export default function GasTracker() {
-

Transaction Cost Estimates

+

+ {t("gasTracker.transactionCostEstimates")} +

- Estimated costs at current gas prices + {t("gasTracker.estimatedCostsAtCurrent")} {price && ` (${currency} @ $${price.toFixed(2)})`}

-
Transaction Type
-
Gas
-
Low
-
Avg
-
High
+
{t("gasTracker.transactionType")}
+
{t("gasTracker.gas")}
+
{t("gasTracker.low")}
+
{t("gasTracker.average")}
+
{t("gasTracker.high")}
{TX_GAS_ESTIMATES.map((tx) => { @@ -210,10 +246,10 @@ export default function GasTracker() { const highCost = calculateTxCost(gasPrices.high, tx.gas, price); return ( -
+
- {tx.name} - {tx.description} + {t(tx.nameKey)} + {t(tx.descriptionKey)}
{tx.gas.toLocaleString()}
@@ -244,7 +280,9 @@ export default function GasTracker() { {data.lastUpdated && (
- Last updated: {new Date(data.lastUpdated).toLocaleTimeString()} + {t("gasTracker.lastUpdated", { + time: new Date(data.lastUpdated).toLocaleTimeString(), + })}
)}
diff --git a/src/components/pages/evm/network/NetworkStatsDisplay.tsx b/src/components/pages/evm/network/NetworkStatsDisplay.tsx index 2a80896..c6e3f65 100644 --- a/src/components/pages/evm/network/NetworkStatsDisplay.tsx +++ b/src/components/pages/evm/network/NetworkStatsDisplay.tsx @@ -1,7 +1,8 @@ import React from "react"; +import { useTranslation } from "react-i18next"; import type { NetworkStats, RPCMetadata } from "../../../../types"; +import { logger } from "../../../../utils/logger"; import { RPCIndicator } from "../../../common/RPCIndicator"; -import { useTranslation } from "react-i18next"; interface NetworkStatsDisplayProps { networkStats: NetworkStats | null; @@ -79,7 +80,7 @@ const NetworkStatsDisplay: React.FC = React.memo( try { return networkStats.metadata.clientVersion || null; } catch (err) { - console.error("Failed to parse metadata:", err); + logger.error("Failed to parse metadata:", err); return null; } }; @@ -102,7 +103,7 @@ const NetworkStatsDisplay: React.FC = React.memo( blockHash: forked.forkBlockHash, }; } catch (err) { - console.error("Failed to parse forked network info:", err); + logger.error("Failed to parse forked network info:", err); return null; } }; diff --git a/src/components/pages/evm/tokenDetails/ERC1155TokenDisplay.tsx b/src/components/pages/evm/tokenDetails/ERC1155TokenDisplay.tsx index dda8442..a7679c1 100644 --- a/src/components/pages/evm/tokenDetails/ERC1155TokenDisplay.tsx +++ b/src/components/pages/evm/tokenDetails/ERC1155TokenDisplay.tsx @@ -1,5 +1,6 @@ import type React from "react"; import { useCallback, useContext, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Link, useParams } from "react-router-dom"; import { AppContext } from "../../../../context"; import { @@ -10,8 +11,8 @@ import { fetchTokenBalance, getImageUrl, } from "../../../../utils/erc1155Metadata"; +import { logger } from "../../../../utils/logger"; import Loader from "../../../common/Loader"; -import { useTranslation } from "react-i18next"; const ERC1155TokenDetails: React.FC = () => { const { @@ -70,7 +71,7 @@ const ERC1155TokenDetails: React.FC = () => { setCollectionInfo(collectionResult); }) .catch((err) => { - console.error("Error fetching metadata:", err); + logger.error("Error fetching metadata:", err); setError(err.message || t("tokenDataFetchFail")); }) .finally(() => setLoading(false)); diff --git a/src/components/pages/evm/tokenDetails/ERC721TokenDisplay.tsx b/src/components/pages/evm/tokenDetails/ERC721TokenDisplay.tsx index 8d81e80..b90c4af 100644 --- a/src/components/pages/evm/tokenDetails/ERC721TokenDisplay.tsx +++ b/src/components/pages/evm/tokenDetails/ERC721TokenDisplay.tsx @@ -1,5 +1,6 @@ import type React from "react"; import { useContext, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Link, useParams } from "react-router-dom"; import { AppContext } from "../../../../context"; import { @@ -11,8 +12,8 @@ import { fetchTokenOwner, getImageUrl, } from "../../../../utils/erc721Metadata"; +import { logger } from "../../../../utils/logger"; import Loader from "../../../common/Loader"; -import { useTranslation } from "react-i18next"; const ERC721TokenDisplay: React.FC = () => { const { @@ -74,7 +75,7 @@ const ERC721TokenDisplay: React.FC = () => { } }) .catch((err) => { - console.error("Error fetching token data:", err); + logger.error("Error fetching token data:", err); setError(err.message || t("tokenDataFetchFail")); // "Failed to fetch token data" }) .finally(() => setLoading(false)); diff --git a/src/components/pages/evm/tx/TransactionDisplay.tsx b/src/components/pages/evm/tx/TransactionDisplay.tsx index b4b8c74..ec44b3b 100644 --- a/src/components/pages/evm/tx/TransactionDisplay.tsx +++ b/src/components/pages/evm/tx/TransactionDisplay.tsx @@ -7,6 +7,7 @@ import { AppContext } from "../../../../context"; import { useSourcify } from "../../../../hooks/useSourcify"; import type { DataService } from "../../../../services/DataService"; import type { TraceResult } from "../../../../services/adapters/NetworkAdapter"; +import { logger } from "../../../../utils/logger"; import type { RPCMetadata, Transaction, @@ -131,7 +132,7 @@ const TransactionDisplay: React.FC = React.memo( setTraceData(trace); setCallTrace(call); }) - .catch((err) => console.error("Error loading trace:", err)) + .catch((err) => logger.error("Error loading trace:", err)) .finally(() => setLoadingTrace(false)); } }, [showTrace, isTraceAvailable, dataService, transaction.hash, traceData, callTrace]); diff --git a/src/components/pages/evm/tx/index.tsx b/src/components/pages/evm/tx/index.tsx index 1ea2b96..0ddc236 100644 --- a/src/components/pages/evm/tx/index.tsx +++ b/src/components/pages/evm/tx/index.tsx @@ -5,6 +5,7 @@ import { useDataService } from "../../../../hooks/useDataService"; import { useProviderSelection } from "../../../../hooks/useProviderSelection"; import { useSelectedData } from "../../../../hooks/useSelectedData"; import type { DataWithMetadata, Transaction } from "../../../../types"; +import { logger } from "../../../../utils/logger"; import Loader from "../../../common/Loader"; import TransactionDisplay from "./TransactionDisplay"; @@ -40,7 +41,7 @@ export default function Tx() { return; } - console.log("Fetching transaction:", txHash, "for chain:", numericNetworkId); + logger.debug("Fetching transaction:", txHash, "for chain:", numericNetworkId); setLoading(true); setError(null); @@ -49,13 +50,13 @@ export default function Tx() { dataService.networkAdapter.getLatestBlockNumber(), ]) .then(([result, latestBlock]) => { - console.log("Fetched transaction:", result); - console.log("Latest block number:", latestBlock); + logger.debug("Fetched transaction:", result); + logger.debug("Latest block number:", latestBlock); setTransactionResult(result); setCurrentBlockNumber(latestBlock); }) .catch((err) => { - console.error("Error fetching transaction:", err); + logger.error("Error fetching transaction:", err); setError(err.message || "Failed to fetch transaction"); }) .finally(() => setLoading(false)); diff --git a/src/components/pages/evm/txs/index.tsx b/src/components/pages/evm/txs/index.tsx index 4da7430..ece376a 100644 --- a/src/components/pages/evm/txs/index.tsx +++ b/src/components/pages/evm/txs/index.tsx @@ -1,11 +1,12 @@ import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom"; import { RPCIndicator } from "../../../../components/common/RPCIndicator"; import { useDataService } from "../../../../hooks/useDataService"; import { useProviderSelection } from "../../../../hooks/useProviderSelection"; import type { DataWithMetadata, Transaction } from "../../../../types"; +import { logger } from "../../../../utils/logger"; import Loader from "../../../common/Loader"; -import { useTranslation } from "react-i18next"; const BLOCKS_PER_PAGE = 10; @@ -91,11 +92,11 @@ export default function Txs() { BLOCKS_PER_PAGE, ); - console.log("Fetched transactions:", fetchedTransactions); + logger.debug("Fetched transactions:", fetchedTransactions); setTransactionsResult(fetchedTransactions); // biome-ignore lint/suspicious/noExplicitAny: } catch (err: any) { - console.error("Error fetching transactions:", err); + logger.error("Error fetching transactions:", err); setError(err.message || t("txs.failedToFetch")); } finally { setLoading(false); diff --git a/src/components/pages/settings/index.tsx b/src/components/pages/settings/index.tsx index b522ad5..aa63343 100644 --- a/src/components/pages/settings/index.tsx +++ b/src/components/pages/settings/index.tsx @@ -9,6 +9,7 @@ import { useMetaMaskExplorer } from "../../../hooks/useMetaMaskExplorer"; import { SUPPORTED_LANGUAGES } from "../../../i18n"; import { clearSupportersCache } from "../../../services/MetadataService"; import type { RPCUrls, RpcUrlsContextType } from "../../../types"; +import { logger } from "../../../utils/logger"; import { getChainIdFromNetwork } from "../../../utils/networkResolver"; // Infura network slugs by chain ID @@ -140,7 +141,7 @@ const Settings: React.FC = () => { } } } catch (e) { - console.warn("Could not clear IndexedDB:", e); + logger.warn("Could not clear IndexedDB:", e); } } @@ -150,7 +151,7 @@ const Settings: React.FC = () => { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map((name) => caches.delete(name))); } catch (e) { - console.warn("Could not clear Cache Storage:", e); + logger.warn("Could not clear Cache Storage:", e); } } @@ -160,7 +161,7 @@ const Settings: React.FC = () => { const registrations = await navigator.serviceWorker.getRegistrations(); await Promise.all(registrations.map((reg) => reg.unregister())); } catch (e) { - console.warn("Could not unregister service workers:", e); + logger.warn("Could not unregister service workers:", e); } } @@ -234,7 +235,7 @@ const Settings: React.FC = () => { }; }); } catch (error) { - console.error("Error fetching from Chainlist:", error); + logger.error("Error fetching from Chainlist:", error); } finally { setFetchingNetworkId(null); } diff --git a/src/config/networks.ts b/src/config/networks.ts index 01d67af..c3c27b3 100644 --- a/src/config/networks.ts +++ b/src/config/networks.ts @@ -5,6 +5,7 @@ import { getNetworkLogoUrl } from "../services/MetadataService"; import type { NetworkConfig, NetworkType } from "../types"; +import { logger } from "../utils/logger"; import { resolveNetwork, extractChainIdFromNetworkId, @@ -74,7 +75,7 @@ const loadedNetworks: NetworkConfig[] = (networksData.networks as NetworkMetadat ); const networksUpdatedAt: string = networksData.updatedAt; -console.log( +logger.debug( `Loaded ${loadedNetworks.length} networks from local config (updated: ${networksUpdatedAt})`, ); diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx index 080946f..656de38 100644 --- a/src/context/AppContext.tsx +++ b/src/context/AppContext.tsx @@ -16,8 +16,9 @@ import { } from "../config/networks"; import { useWagmiConnection } from "../hooks/useWagmiConnection"; import type { IAppContext, NetworkConfig, RpcUrlsContextType } from "../types"; -import { getChainIdFromNetwork } from "../utils/networkResolver"; import { loadJsonFilesFromStorage, saveJsonFilesToStorage } from "../utils/artifactsStorage"; +import { logger } from "../utils/logger"; +import { getChainIdFromNetwork } from "../utils/networkResolver"; import { getEffectiveRpcUrls, saveRpcUrlsToStorage } from "../utils/rpcStorage"; // Alias exported for use across the app where a shorter/consistent name is preferred @@ -63,7 +64,7 @@ export const AppContextProvider = ({ children }: { children: ReactNode }) => { try { saveRpcUrlsToStorage(next); } catch (err) { - console.warn("Failed to persist rpc urls", err); + logger.warn("Failed to persist rpc urls", err); } }, []); @@ -73,7 +74,7 @@ export const AppContextProvider = ({ children }: { children: ReactNode }) => { try { saveJsonFilesToStorage(next); } catch (err) { - console.warn("Failed to persist json files", err); + logger.warn("Failed to persist json files", err); } }, []); @@ -193,7 +194,7 @@ export const AppContextProvider = ({ children }: { children: ReactNode }) => { // Mark app as ready setAppReady(true); } catch (error) { - console.error("Error initializing app:", error); + logger.error("Error initializing app:", error); setAppReady(true); // Still mark as ready even if there's an error } }; diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index 426f92a..0777c3a 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -1,5 +1,6 @@ import React, { createContext, type ReactNode, useContext, useEffect, useState } from "react"; import { DEFAULT_SETTINGS, type UserSettings } from "../types"; +import { logger } from "../utils/logger"; interface SettingsContextType { settings: UserSettings; @@ -32,7 +33,7 @@ export const SettingsProvider = React.memo(({ children }) return { ...DEFAULT_SETTINGS, ...parsed }; } } catch (error) { - console.warn("Failed to load settings from localStorage:", error); + logger.warn("Failed to load settings from localStorage:", error); } return DEFAULT_SETTINGS; }); @@ -64,7 +65,7 @@ export const SettingsProvider = React.memo(({ children }) try { localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings)); } catch (error) { - console.warn("Failed to save settings to localStorage:", error); + logger.warn("Failed to save settings to localStorage:", error); } }, [settings]); diff --git a/src/hooks/useENS.ts b/src/hooks/useENS.ts index a1a0e21..efcc7c5 100644 --- a/src/hooks/useENS.ts +++ b/src/hooks/useENS.ts @@ -2,6 +2,7 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { AppContext } from "../context"; import { ENSService } from "../services/ENS/ENSService"; +import { logger } from "../utils/logger"; import type { DecodedContenthash, ENSRecords, ENSReverseResult } from "../types"; interface UseENSResult { @@ -103,7 +104,7 @@ export function useENS( } } } catch (err) { - console.error("Error fetching ENS data:", err); + logger.error("Error fetching ENS data:", err); setError(err instanceof Error ? err.message : "Unknown error"); } finally { setLoading(false); diff --git a/src/hooks/useProviderSelection.ts b/src/hooks/useProviderSelection.ts index 2f2bfff..021bba0 100644 --- a/src/hooks/useProviderSelection.ts +++ b/src/hooks/useProviderSelection.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { logger } from "../utils/logger"; const STORAGE_KEY_PREFIX = "rpc_selected_provider_"; @@ -27,13 +28,13 @@ export function useProviderSelection( try { sessionStorage.setItem(storageKey, selectedProvider); } catch (error) { - console.warn("Failed to save provider selection:", error); + logger.warn("Failed to save provider selection:", error); } } else { try { sessionStorage.removeItem(storageKey); } catch (error) { - console.warn("Failed to remove provider selection:", error); + logger.warn("Failed to remove provider selection:", error); } } }, [selectedProvider, storageKey]); diff --git a/src/hooks/useSelectedData.ts b/src/hooks/useSelectedData.ts index a0b0570..83e0ae2 100644 --- a/src/hooks/useSelectedData.ts +++ b/src/hooks/useSelectedData.ts @@ -1,5 +1,6 @@ import { useMemo } from "react"; import type { DataWithMetadata } from "../types"; +import { logger } from "../utils/logger"; /** * Hook to extract the correct data based on selected provider @@ -35,7 +36,7 @@ export function useSelectedData( if (!providerResponse || !providerResponse.data) { // Fallback to default if selected provider not found - console.warn(`Selected provider ${selectedProvider} not found or failed, using default`); + logger.warn(`Selected provider ${selectedProvider} not found or failed, using default`); return result.data; } diff --git a/src/hooks/useSourcify.ts b/src/hooks/useSourcify.ts index b6d0601..41afb7e 100644 --- a/src/hooks/useSourcify.ts +++ b/src/hooks/useSourcify.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { logger } from "../utils/logger"; export interface SourcifyMatch { match: "perfect" | "partial" | null; @@ -84,7 +85,7 @@ export const useSourcify = ( setData(contractData); setIsVerified(!!contractData.match); } catch (err) { - console.error("Error fetching Sourcify data:", err); + logger.error("Error fetching Sourcify data:", err); setError(err instanceof Error ? err.message : "Unknown error occurred"); setIsVerified(false); setData(null); @@ -167,7 +168,7 @@ export const useSourcifyFiles = ( setFiles(fileContents); } } catch (err) { - console.error("Error fetching Sourcify files:", err); + logger.error("Error fetching Sourcify files:", err); setError(err instanceof Error ? err.message : "Unknown error occurred"); setFiles([]); } finally { @@ -206,7 +207,7 @@ export const checkSourcifyVerification = async ( } return false; } catch (err) { - console.error("Error checking Sourcify verification:", err); + logger.error("Error checking Sourcify verification:", err); return false; } }; diff --git a/src/hooks/useZipJsonReader.tsx b/src/hooks/useZipJsonReader.tsx index 33e37f4..76504ac 100644 --- a/src/hooks/useZipJsonReader.tsx +++ b/src/hooks/useZipJsonReader.tsx @@ -1,5 +1,6 @@ import JSZip from "jszip"; import { useState } from "react"; +import { logger } from "../utils/logger"; export function useZipJsonReader() { const [loading, setLoading] = useState(false); @@ -14,7 +15,7 @@ export function useZipJsonReader() { // biome-ignore lint/suspicious/noExplicitAny: const results: any = {}; const entries = Object.keys(zip.files); - console.log(entries); + logger.debug(entries); // biome-ignore lint/suspicious/noExplicitAny: let deployments: any = {}; @@ -69,7 +70,7 @@ export function useZipJsonReader() { } setLoading(false); - console.log(results); + logger.debug(results); // Build an address -> artifact map based on each entry's `deployments` // biome-ignore lint/suspicious/noExplicitAny: @@ -118,10 +119,10 @@ export function useZipJsonReader() { } } - console.log(addressMap); + logger.debug(addressMap); return addressMap; } catch (err) { - console.error("Failed to process ZIP file:", err); + logger.error("Failed to process ZIP file:", err); setError("Failed to process ZIP file. Please ensure it's a valid ZIP archive."); setLoading(false); return {}; diff --git a/src/i18n.ts b/src/i18n.ts index 77a1715..e573575 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -6,7 +6,6 @@ import enAddress from "./locales/en/address.json"; import enBlock from "./locales/en/block.json"; import enCommon from "./locales/en/common.json"; import enDevtools from "./locales/en/devtools.json"; -import enErrors from "./locales/en/errors.json"; import enHome from "./locales/en/home.json"; import enNetwork from "./locales/en/network.json"; import enSettings from "./locales/en/settings.json"; @@ -17,7 +16,6 @@ import esAddress from "./locales/es/address.json"; import esBlock from "./locales/es/block.json"; import esCommon from "./locales/es/common.json"; import esDevtools from "./locales/es/devtools.json"; -import esErrors from "./locales/es/errors.json"; import esHome from "./locales/es/home.json"; import esNetwork from "./locales/es/network.json"; import esSettings from "./locales/es/settings.json"; @@ -43,7 +41,6 @@ i18n transaction: enTransaction, devtools: enDevtools, network: enNetwork, - errors: enErrors, tokenDetails: enTokenDetails, }, es: { @@ -55,23 +52,12 @@ i18n transaction: esTransaction, devtools: esDevtools, network: esNetwork, - errors: esErrors, tokenDetails: esTokenDetails, }, }, fallbackLng: "en", defaultNS: "common", - ns: [ - "common", - "home", - "settings", - "address", - "block", - "transaction", - "devtools", - "network", - "errors", - ], + ns: ["common", "home", "settings", "address", "block", "transaction", "devtools", "network"], interpolation: { escapeValue: false, }, diff --git a/src/i18next.d.ts b/src/i18next.d.ts index ff993e0..f5b94b0 100644 --- a/src/i18next.d.ts +++ b/src/i18next.d.ts @@ -2,7 +2,6 @@ import type address from "./locales/en/address.json"; import type block from "./locales/en/block.json"; import type common from "./locales/en/common.json"; import type devtools from "./locales/en/devtools.json"; -import type errors from "./locales/en/errors.json"; import type home from "./locales/en/home.json"; import type network from "./locales/en/network.json"; import type settings from "./locales/en/settings.json"; @@ -20,7 +19,6 @@ declare module "i18next" { transaction: typeof transaction; devtools: typeof devtools; network: typeof network; - errors: typeof errors; tokenDetails: typeof tokenDetails; }; } diff --git a/src/locales/en/address.json b/src/locales/en/address.json index 9ed4dcb..41f5c47 100644 --- a/src/locales/en/address.json +++ b/src/locales/en/address.json @@ -1,9 +1,5 @@ { - "title": "Address", - "type": "Type", "balance": "Balance", - "transactions": "Transactions", - "contractVerified": "Contract Verified", "contractName": "Contract Name", "compiler": "Compiler", "contractDetails": "Contract Details", @@ -11,75 +7,48 @@ "rawAbi": "Raw ABI", "functions": "Functions", "contractBytecode": "Contract Bytecode", - "contractStorage": "Contract Storage", - "storageSlot": "Storage Slot", "value": "Value", - "get": "Get", - "thisAddress": "This Address", - "contractCreation": "Contract Creation", - "noTransactionsFound": "No transactions found for this address", - "typeEOA": "Externally Owned Account (EOA)", - "typeContract": "Contract", "checkingSourcify": "Checking Sourcify...", "verified": "Verified", - "perfectMatch": "Perfect Match", - "partialMatch": "Partial Match", - "localJson": "Local JSON", "notVerified": "Not Verified", "resolvingEns": "Resolving ENS name...", - "errorResolvingEns": "Could not resolve ENS name", "noAddressProvided": "No address provided", "addressDataNotFound": "Address data not found", "loadingAddressData": "Loading address data...", "detectingAddressType": "Detecting address type...", "sourcifyLink": "View Full Contract on Sourcify", - "viewOnSourcify": "View on Sourcify", "overview": "Overview", - "txnCountNonce": "Txn Count (Nonce)", "eip7702Delegate": "EIP-7702 Delegate", "loading": "Loading...", "contractInfo": "Contract Info", "status": "Status", - "verifiedBadge": "Verified", "evmVersion": "EVM Version", "sourcify": "Sourcify", "tokenInfo": "Token Info", "token": "Token", "decimals": "Decimals", "totalSupply": "Total Supply", - "nftCollection": "NFT Collection", - "multiTokenCollection": "Multi-Token Collection", "collection": "Collection", "tokenStandard": "Token Standard", "totalMinted": "Total Minted", "metadataURI": "Metadata URI", - "viewNft": "View NFT", - "viewToken": "View Token", "view": "View", "moreInfo": "More Info", "transactionHistory": "Transaction History", "searchTransactions": "Search Transactions", - "searchForTransactions": "Search for transactions by scanning the blockchain for state changes.", "last5Txs": "Last 5 transactions", "last10Txs": "Last 10 transactions", "last50Txs": "Last 50 transactions", "allTxs": "All transactions", "searchFullHistory": "Search full history", - "allTransactions": "All transactions", "noteSearching": "Note: Searching all transactions may take longer for active addresses.", "txHistory": "Transaction History", "searching": "Searching...", "foundTransactions": "Found {{count}} transactions...", "searchingForTxs": "Searching for transactions...", - "binarySearching": "Binary searching through blockchain state to find address activity", "completeHistory": "Complete history ({{count}} transactions)", "completeHistory_other": "Complete history ({{count}} transactions)", - "partialHistory": "Partial (logs only) - {{count}} transactions", "noDataAvailable": "No data available", - "showingFromBlocks": "Showing {{count}} transactions from blocks {{oldest}} to {{newest}}", - "foundAll": "Found all {{count}} transactions", - "found": "Found {{count}} transactions", - "searchRecent": "Search Recent Transactions", "loadMore": "Load More", "more5": "5 more transactions", "more10": "10 more transactions", @@ -90,20 +59,9 @@ "showing": "Showing {{count}} transaction", "showing_other": "Showing {{count}} transactions", "cancel": "Cancel", - "nftToken": "NFT Token", - "detectingTokenType": "Detecting token type...", - "readContract": "Read Contract", - "writeContract": "Write Contract", - "connect": "Connect", "write": "Write", - "read": "Read", - "result": "Result", "error": "Error", "query": "Query", - "send": "Send", - "payableAmount": "Payable Amount (ETH)", - "noReadFunctions": "No read functions available", - "noWriteFunctions": "No write functions available", "compilerVersion": "Compiler Version", "evmVersionLabel": "EVM Version", "networkIdLabel": "Network ID", @@ -147,10 +105,6 @@ "viewOnEnsApp": "View on ENS App", "fetchCustomToken": "Fetch Custom Token", "tokenContractAddress": "Token Contract Address", - "invalidAddressFormat": "Invalid address format", - "noRpcAvailable": "No RPC URL available for this network", - "notErc20Token": "This address does not appear to be an ERC20 token", - "failedToFetchTokenInfo": "Failed to fetch token information", "fetchingTokenInfo": "Fetching token information...", "tokenDetails": "Token Details", "nameLabel": "Name", @@ -170,18 +124,7 @@ "ally": "Ally", "fetchPopularTokens": "Fetch Popular Tokens", "popularTokensLoaded": "Popular Tokens Loaded", - "failedToFetchTokenHoldings": "Failed to fetch token holdings", - "failedToFetchPopularTokens": "Failed to fetch popular tokens", - "notSet": "Not set", - "nonceTxSent": "Nonce (Transactions Sent)", "txns": "txns", - "enterTokenId": "Enter Token ID", - "nfts": "NFTs", - "balanceSectionTitle": "Balance", - "viewNftLabel": "View NFT", - "viewTokenLabel": "View Token", - "sourceCodeWithCount": "Source Code ({{count}} file)", - "sourceCodeWithCount_plural": "Source Code ({{count}} files)", "details": { "txs": "Transactions", "readFunctions": "Read Functions", @@ -198,7 +141,6 @@ "notVerified": "Not Verified", "contractName": "Contract Name", "compiler": "Compiler", - "contractDetails": "Contract Details", "evmVersion": "EVM Version", "verifiedAt": "Verified At", "matchType": "Match Type", @@ -210,7 +152,6 @@ "connectWallet": "Connect Wallet", "wrongNetwork": "Wrong Network", "contractDetail": "Contract Detail", - "noParametrsRequired": "No parameters required", "confirmed": "✅ Transaction confirmed!", "viewTx": "View transaction", "confirmingWallet": "Confirming in Wallet...", diff --git a/src/locales/en/block.json b/src/locales/en/block.json index 1b33ddb..d3c693b 100644 --- a/src/locales/en/block.json +++ b/src/locales/en/block.json @@ -21,24 +21,11 @@ "l1BlockNumber": "L1 Block Number:", "sendCount": "Send Count:", "sendRoot": "Send Root:", - "hash": "Hash:", - "parentHash": "Parent Hash:", - "stateRoot": "State Root:", - "transactionsRoot": "Transactions Root:", - "receiptsRoot": "Receipts Root:", - "withdrawalsRoot": "Withdrawals Root:", - "logsBloom": "Logs Bloom:", - "nonce": "Nonce:", - "mixHash": "Mix Hash:", - "sha3Uncles": "Sha3 Uncles:", - "showMoreDetails": "+ Show More Details", - "hideMoreDetails": "- Hide More Details", "previousBlock": "Previous block", "nextBlock": "Next block", "validator": "Validator", "address": "Address", "amount": "Amount", - "timestamp": "Timestamp:", "latestBlocks": "Latest Blocks", "loadingBlocks": "Loading blocks...", "showingRecent": "Showing {{count}} most recent blocks", @@ -59,16 +46,16 @@ "time": { "justNow": "just now", "inFewSeconds": "in a few seconds", + "second": "second", + "second_other": "seconds", + "ago": "{{count}} {{unit}} ago", + "in": "in {{count}} {{unit}}", "day": "day", "day_other": "days", "hour": "hour", "hour_other": "hours", "minute": "minute", - "minute_other": "minutes", - "second": "second", - "second_other": "seconds", - "ago": "{{count}} {{unit}} ago", - "in": "in {{count}} {{unit}}" + "minute_other": "minutes" }, "errors": { "failedToFetchBlock": "Failed to fetch block data", diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 2a03bd0..a228ee8 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -1,13 +1,9 @@ { "nav": { - "blocks": "BLOCKS", - "transactions": "TRANSACTIONS", "searchPlaceholderDesktop": "Search by Address / Tx Hash / Block / ENS", "searchPlaceholderMobile": "Search address, tx, block, ENS...", "searchAriaLabel": "Search", "searchTitle": "Search", - "devToolsAriaLabel": "Dev Tools", - "devToolsTitle": "Dev Tools", "switchToLightMode": "Switch to light mode", "switchToDarkMode": "Switch to dark mode", "lightModeTitle": "Light mode", @@ -18,7 +14,6 @@ "closeMenuAriaLabel": "Close menu", "blocksMobile": "Blocks", "transactionsMobile": "Transactions", - "gasTracker": "Gas Tracker", "home": "Home", "lightMode": "Light Mode", "darkMode": "Dark Mode", @@ -233,7 +228,6 @@ "backers": "Backers" }, "profile": { - "title": "Profile", "notFound": "Profile Not Found", "loading": "Loading profile...", "invalidUrl": "Invalid profile URL. Please provide both profile type and ID.", @@ -244,9 +238,7 @@ }, "extraData": { "showDecoded": "Show decoded", - "showRawHex": "Show raw hex", - "utf8": "UTF-8", - "hex": "Hex" + "showRawHex": "Show raw hex" }, "searchBox": { "placeholder": "Search by Address / Txn Hash / Block / ENS Name", @@ -264,7 +256,7 @@ "rpcProviders": "RPC Providers", "strategy": "Strategy: {{strategy}}", "succeededOnAttempt": "Succeeded on attempt #{{count}}", - "fastestResponseWins": "Fastest response wins \u2022 All times shown", + "fastestResponseWins": "Fastest response wins • All times shown", "responsesDiffer": "Responses differ between providers", "selected": "Selected", "winner": "Winner", diff --git a/src/locales/en/errors.json b/src/locales/en/errors.json deleted file mode 100644 index 16a9d69..0000000 --- a/src/locales/en/errors.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "failedToFetchBlock": "Failed to fetch block", - "blockNotFound": "Block not found", - "failedToFetchTransaction": "Failed to fetch transaction", - "transactionNotFound": "Transaction not found", - "failedToFetchAddress": "Failed to fetch address data", - "noRpcConfigured": "No RPC URL configured for chain {{chainId}}", - "failedToFetchTransactions": "Failed to fetch transactions", - "failedToFetchTransactionHistory": "Failed to fetch transaction history: {{error}}", - "invalidEthValue": "Invalid ETH value", - "errorWritingContract": "Error: {{error}}", - "errorReadingContract": "Error reading from contract: {{error}}", - "errorCallingContract": "Contract call failed", - "pleaseProvideValue": "Please provide value for {{param}}", - "errorHeading": "Error", - "invalidProfileUrl": "Invalid profile URL. Please provide both profile type and ID.", - "invalidProfileType": "Invalid profile type: {{type}}. Must be one of: network, app, organization.", - "profileNotFound": "Profile not found: {{type}}/{{id}}", - "failedToFetchProfile": "Failed to fetch profile", - "invalidSearchQuery": "Invalid search query. Enter an address (0x...), transaction hash, block number, or ENS name.", - "noSearchQuery": "No search query provided", - "ensResolutionError": "Error resolving ENS: {{error}}", - "noMainnetRpc": "No Ethereum mainnet RPC configured for ENS resolution", - "failedToFetchSupporters": "Failed to fetch supporters" -} diff --git a/src/locales/en/home.json b/src/locales/en/home.json index 95d7400..bfaf5b9 100644 --- a/src/locales/en/home.json +++ b/src/locales/en/home.json @@ -1,7 +1,6 @@ { "title": "OPENSCAN", "loading": "Loading networks...", - "networkId": "Network ID: {{networkId}}", "showTestnets": "Show testnets", "hideTestnets": "Hide testnets", "invalidSearchTerm": "Invalid search. Enter an address (0x...), transaction hash, block number, or ENS name.", diff --git a/src/locales/en/settings.json b/src/locales/en/settings.json index 311acf7..42ee543 100644 --- a/src/locales/en/settings.json +++ b/src/locales/en/settings.json @@ -78,11 +78,7 @@ "description": "Configure RPC URLs for each network. Click on a network to expand and configure its endpoints.", "fetchButton": "Fetch from Chainlist", "fetchingButton": "Fetching...", - "placeholder": "https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY", "currentRPCs": "Current RPCs:", - "chainId": "Chain ID: {{chainId}}", - "rpcCount_one": "{{count}} RPC", - "rpcCount_other": "{{count}} RPCs", "removeRpc": "Remove RPC", "localhostHelp": "Need to access your local network remotely?", "localhostHelpLink": "Learn how to set up a tunnel with ngrok", @@ -94,11 +90,5 @@ "configuredText": "Configured!", "useAsDefault": "Use as default explorer" } - }, - "errors": { - "failedToFetch": "Failed to fetch from Chainlist", - "noRPCsFound": "No RPCs found for chain {{chainId}}", - "noPrivacyFriendlyRPCs": "No privacy-friendly RPCs found for chain {{chainId}}", - "errorFetchingChainlist": "Error fetching from Chainlist:" } } diff --git a/src/locales/en/tokenDetails.json b/src/locales/en/tokenDetails.json index 8f45146..f4ebbc5 100644 --- a/src/locales/en/tokenDetails.json +++ b/src/locales/en/tokenDetails.json @@ -29,9 +29,7 @@ "checkBalance": "Check Balance", "balance": "Balance", "detectingTokenType": "Detecting token type...", - "unknown": "unknown", "errors": { - "missingValues": "Missing contract address, token ID, or RPC URL", "error": "Error" } } diff --git a/src/locales/en/transaction.json b/src/locales/en/transaction.json index 2d9d6f8..59fac54 100644 --- a/src/locales/en/transaction.json +++ b/src/locales/en/transaction.json @@ -6,7 +6,6 @@ "failed": "Failed", "block": "Block:", "blockConfirmations": "Block Confirmations", - "confirmations": "Confirmations", "timestamp": "Timestamp:", "from": "From:", "to": "To:", @@ -25,7 +24,6 @@ "inputData": "Input Data:", "decodedInput": "Decoded Input:", "eventLogs": "Event Logs:", - "debugTrace": "Debug Trace", "showDebugTrace": "Show Debug Trace", "hideDebugTrace": "Hide Debug Trace", "loadingTrace": "Loading trace data...", @@ -54,19 +52,6 @@ "logsData": "Data", "logsIndexed": "indexed", "logsAbi": "ABI", - "latestTransactions": "Latest Transactions", - "loadingTransactions": "Loading transactions...", - "showingFromLastBlocks": "Showing {{count}} transactions from the last {{blocks}} blocks", - "showingFromRange": "Showing {{count}} transactions from blocks {{from}} - {{to}}", - "noTransactionsFound": "No transactions found in the selected block range", - "tableHash": "Tx Hash", - "tableBlock": "Block", - "tableFrom": "From", - "tableTo": "To", - "tableValue": "Value", - "tableGasPrice": "Gas Price", - "tableGas": "Gas", - "mempool": "Mempool", "transaction": "Transaction", "loadingTransaction": "Loading transaction...", "transactionNotFound": "Transaction not found", @@ -99,10 +84,8 @@ "pagination": { "latest": "Latest", "latestTitle": "Go to latest transactions", - "newer": "← Newer", "newerTitle": "View newer transactions", - "older": "Older →", "olderTitle": "View older transactions" } diff --git a/src/locales/es/address.json b/src/locales/es/address.json index d83d721..a8ead65 100644 --- a/src/locales/es/address.json +++ b/src/locales/es/address.json @@ -1,85 +1,54 @@ { - "title": "Dirección", - "type": "Tipo", "balance": "Balance", - "transactions": "Transacciones", - "contractVerified": "Contrato verificado", "contractName": "Nombre del contrato", "compiler": "Compilador", "contractDetails": "Detalles del contrato", "sourceCode": "Código fuente", - "rawAbi": "ABI crudo", + "rawAbi": "Raw ABI", "functions": "Funciones", "contractBytecode": "Bytecode del contrato", - "contractStorage": "Almacenamiento del contrato", - "storageSlot": "Slot de almacenamiento", "value": "Valor", - "get": "Obtener", - "thisAddress": "Esta dirección", - "contractCreation": "Creación del contrato", - "noTransactionsFound": "No se encontraron transacciones para esta dirección", - "typeEOA": "Cuenta de propiedad externa (EOA)", - "typeContract": "Contrato", "checkingSourcify": "Verificando en Sourcify...", "verified": "Verificado", - "perfectMatch": "Coincidencia perfecta", - "partialMatch": "Coincidencia parcial", - "localJson": "JSON local", "notVerified": "No verificado", "resolvingEns": "Resolviendo nombre ENS...", - "errorResolvingEns": "No se pudo resolver el nombre ENS", "noAddressProvided": "No se proporcionó ninguna dirección", "addressDataNotFound": "No se encontraron datos para la dirección", "loadingAddressData": "Cargando datos de la dirección...", "detectingAddressType": "Detectando tipo de dirección...", "sourcifyLink": "Ver contrato completo en Sourcify", - "viewOnSourcify": "Ver en Sourcify", "overview": "Resumen", - "txnCountNonce": "Cantidad de transacciones (Nonce)", "eip7702Delegate": "Delegado EIP-7702", "loading": "Cargando...", "contractInfo": "Información del contrato", "status": "Estado", - "verifiedBadge": "Verificado", "evmVersion": "Versión de la EVM", "sourcify": "Sourcify", "tokenInfo": "Información del token", "token": "Token", "decimals": "Decimales", "totalSupply": "Suministro total", - "nftCollection": "Colección NFT", - "multiTokenCollection": "Colección multi-token", "collection": "Colección", "tokenStandard": "Estándar del token", "totalMinted": "Total minteado", "metadataURI": "URI de metadata", - "viewNft": "Ver NFT", - "viewToken": "Ver token", "view": "Ver", "moreInfo": "Más información", "transactionHistory": "Historial de transacciones", "searchTransactions": "Buscar transacciones", - "searchForTransactions": "Buscar transacciones escaneando la blockchain en busca de cambios de estado.", "last5Txs": "Últimas 5 transacciones", "last10Txs": "Últimas 10 transacciones", "last50Txs": "Últimas 50 transacciones", "allTxs": "Todas las transacciones", - "allTransactions": "Todas las transacciones", "searchFullHistory": "Buscar historial completo", "noteSearching": "Nota: buscar todas las transacciones puede demorar más en direcciones muy activas.", "txHistory": "Historial de transacciones", "searching": "Buscando...", "foundTransactions": "Se encontraron {{count}} transacciones...", "searchingForTxs": "Buscando transacciones...", - "binarySearching": "Búsqueda binaria en el estado de la blockchain para detectar actividad de la dirección", - "completeHistory": "Historial completo ({{count}} transacciones)", + "completeHistory": "Historial completo ({{count}} transacción)", "completeHistory_other": "Historial completo ({{count}} transacciones)", - "partialHistory": "Historial parcial (solo logs) - {{count}} transacciones", "noDataAvailable": "No hay datos disponibles", - "showingFromBlocks": "Mostrando {{count}} transacciones desde los bloques {{oldest}} hasta {{newest}}", - "foundAll": "Se encontraron todas las {{count}} transacciones", - "found": "Se encontraron {{count}} transacciones", - "searchRecent": "Buscar transacciones recientes", "loadMore": "Cargar más", "more5": "5 transacciones más", "more10": "10 transacciones más", @@ -90,20 +59,9 @@ "cancel": "Cancelar", "showing": "Showing {{count}} transaction", "showing_other": "Showing {{count}} transactions", - "nftToken": "Token NFT", - "detectingTokenType": "Detectando tipo de token...", - "readContract": "Leer contrato", - "writeContract": "Escribir contrato", - "connect": "Conectar", "write": "Escribir", - "read": "Leer", - "result": "Resultado", "error": "Error", "query": "Consulta", - "send": "Enviar", - "payableAmount": "Monto payable (ETH)", - "noReadFunctions": "No hay funciones de lectura disponibles", - "noWriteFunctions": "No hay funciones de escritura disponibles", "compilerVersion": "Versión del compilador", "evmVersionLabel": "Versión de la EVM", "networkIdLabel": "ID de red", @@ -147,10 +105,6 @@ "viewOnEnsApp": "Ver en la app de ENS", "fetchCustomToken": "Obtener token personalizado", "tokenContractAddress": "Dirección del contrato del token", - "invalidAddressFormat": "Formato de dirección inválido", - "noRpcAvailable": "No hay una URL RPC disponible para esta red", - "notErc20Token": "Esta dirección no parece ser un token ERC20", - "failedToFetchTokenInfo": "No se pudo obtener la información del token", "fetchingTokenInfo": "Obteniendo información del token...", "tokenDetails": "Detalles del token", "nameLabel": "Nombre", @@ -170,18 +124,7 @@ "ally": "Ally", "fetchPopularTokens": "Obtener tokens populares", "popularTokensLoaded": "Tokens populares cargados", - "failedToFetchTokenHoldings": "No se pudieron obtener las tenencias de tokens", - "failedToFetchPopularTokens": "No se pudieron obtener los tokens populares", - "notSet": "No configurado", - "nonceTxSent": "Nonce (transacciones enviadas)", "txns": "txns", - "enterTokenId": "Ingresar ID del token", - "nfts": "NFTs", - "balanceSectionTitle": "Balance", - "viewNftLabel": "Ver NFT", - "viewTokenLabel": "Ver token", - "sourceCodeWithCount": "Código fuente ({{count}} archivo)", - "sourceCodeWithCount_plural": "Código fuente ({{count}} archivos)", "details": { "txs": "Transacciones", "readFunctions": "Funciones de lectura", @@ -198,7 +141,6 @@ "notVerified": "No verificado", "contractName": "Nombre del contrato", "compiler": "Compilador", - "contractDetails": "Detalles del contrato", "evmVersion": "Versión de la EVM", "verifiedAt": "Verificado el", "matchType": "Tipo de coincidencia", @@ -210,7 +152,6 @@ "connectWallet": "Conectar billetera", "wrongNetwork": "Red incorrecta", "contractDetail": "Detalle del contrato", - "noParametrsRequired": "No requiere parámetros", "confirmed": "✅ ¡Transacción confirmada!", "viewTx": "Ver transacción", "confirmingWallet": "Confirmando en la billetera...", diff --git a/src/locales/es/block.json b/src/locales/es/block.json index d3ba540..c27424d 100644 --- a/src/locales/es/block.json +++ b/src/locales/es/block.json @@ -21,24 +21,11 @@ "l1BlockNumber": "Número de bloque L1:", "sendCount": "Cantidad de envíos:", "sendRoot": "Send root:", - "hash": "Hash:", - "parentHash": "Hash del bloque padre:", - "stateRoot": "State root:", - "transactionsRoot": "Root de transacciones:", - "receiptsRoot": "Root de receipts:", - "withdrawalsRoot": "Root de retiros:", - "logsBloom": "Logs bloom:", - "nonce": "Nonce:", - "mixHash": "Mix hash:", - "sha3Uncles": "Sha3 uncles:", - "showMoreDetails": "+ Mostrar más detalles", - "hideMoreDetails": "- Ocultar detalles", "previousBlock": "Bloque anterior", "nextBlock": "Bloque siguiente", "validator": "Validador", "address": "Dirección", "amount": "Monto", - "timestamp": "Timestamp:", "latestBlocks": "Últimos bloques", "loadingBlocks": "Cargando bloques...", "showingRecent": "Mostrando los {{count}} bloques más recientes", @@ -59,16 +46,16 @@ "time": { "justNow": "recién", "inFewSeconds": "en unos segundos", + "second": "segundo", + "second_other": "segundos", + "ago": "hace {{count}} {{unit}}", + "in": "en {{count}} {{unit}}", "day": "día", "day_other": "días", "hour": "hora", "hour_other": "horas", "minute": "minuto", - "minute_other": "minutos", - "second": "segundo", - "second_other": "segundos", - "ago": "hace {{count}} {{unit}}", - "in": "en {{count}} {{unit}}" + "minute_other": "minutos" }, "errors": { "failedToFetchBlock": "No se pudieron obtener los datos del bloque", diff --git a/src/locales/es/common.json b/src/locales/es/common.json index 7ea3243..1300b00 100644 --- a/src/locales/es/common.json +++ b/src/locales/es/common.json @@ -1,13 +1,9 @@ { "nav": { - "blocks": "BLOQUES", - "transactions": "TRANSACCIONES", "searchPlaceholderDesktop": "Buscar por dirección / hash de tx / bloque / ENS", "searchPlaceholderMobile": "Buscar dirección, tx, bloque, ENS...", "searchAriaLabel": "Buscar", "searchTitle": "Buscar", - "devToolsAriaLabel": "Herramientas dev", - "devToolsTitle": "Herramientas dev", "switchToLightMode": "Cambiar a modo claro", "switchToDarkMode": "Cambiar a modo oscuro", "lightModeTitle": "Modo claro", @@ -18,7 +14,6 @@ "closeMenuAriaLabel": "Cerrar menú", "blocksMobile": "Bloques", "transactionsMobile": "Transacciones", - "gasTracker": "Tracker de gas", "home": "Inicio", "lightMode": "Modo claro", "darkMode": "Modo oscuro", @@ -37,7 +32,7 @@ "about": "Acerca de", "subscribe": "Suscribirse", "contact": "Contacto", - "supporters": "Soportes", + "supporters": "Sponsors", "reportBug": "Reportar bug", "reportBugTitle": "Reportar un bug", "viewCommitTitle": "Ver commit {{hash}} en GitHub", @@ -147,9 +142,157 @@ "paymentAddressDescription": "Todos los pagos se envían directamente a la dirección del DAO de OpenScan:", "submitSubscriptionPR": "Enviar PR de suscripción", "viewDaoAddress": "Ver dirección del DAO", - "footerNote": "Todos los ingresos se reinvierten exclusivamente en el desarrollo y mejora continua de la plataforma." + "footerNote": "Todos los ingresos se reinvierten exclusivamente en el desarrollo y mejora continua de la plataforma.", + "categories": { + "tokens": { + "name": "Tokens", + "description": "Tags de contratos verificados, integración de metadatos, logo, links oficiales e integración con el fetcher de balances." + }, + "networks": { + "name": "Redes", + "description": "Soporte completo de métodos RPC, mantenimiento técnico dedicado, disponibilidad de subdominio y branding destacado." + }, + "apps": { + "name": "Apps cripto", + "description": "Listado dedicado y branding verificado para wallets, dApps, herramientas complementarias del explorador y exchanges, promoviendo integración y visibilidad." + }, + "companies": { + "name": "Empresas y orgs", + "description": "Reconocimiento formal y visibilidad para proveedores de infraestructura, fondos de venture y otras entidades que contribuyen a la continuidad del proyecto." + } + }, + "tierNames": { + "backer": "Backer", + "partner": "Partner", + "ally": "Ally" + }, + "tierBenefits": { + "tokens": { + "backer": [ + "Página de token con info del token: detalles ERC20, nombre del proyecto y URLs personalizadas", + "Contratos verificados y contratos importantes etiquetados" + ], + "partner": [ + "Saldo de token mostrado en el explorador principal", + "Página de perfil simple", + "Listado en múltiples redes" + ], + "ally": [ + "Página de perfil completa con descripción en markdown", + "Subdominio de OpenScan", + "Línea directa de comunicación con el equipo técnico" + ] + }, + "networks": { + "backer": [ + "Perfil en markdown en la página de la red", + "Ubicación prioritaria en la página principal" + ], + "partner": [ + "Explorador de red en subdominio dedicado", + "Línea directa de comunicación con el equipo técnico" + ], + "ally": [ + "Poder de voto en el roadmap", + "Features específicas de la red en el explorador dedicado" + ] + }, + "apps": { + "backer": [ + "Página de perfil simple", + "Contratos importantes verificados y etiquetados", + "Listado de eventos de contratos" + ], + "partner": [ + "Subdominio de OpenScan", + "Página de perfil completa con descripción en markdown" + ], + "ally": [ + "Poder de voto en el roadmap", + "Línea directa de comunicación con el equipo técnico" + ] + }, + "companies": { + "backer": [ + "Página de perfil simple", + "Contratos importantes verificados y etiquetados", + "Listado de eventos de contratos" + ], + "partner": [ + "Página de perfil completa con descripción en markdown", + "Subdominio de OpenScan" + ], + "ally": [ + "Línea directa de comunicación con el equipo técnico", + "Poder de voto en el roadmap" + ] + } + } }, "buildWarning": { "notProductionBuild": "Esta no es una versión verificada de producción" + }, + "extraData": { + "showDecoded": "Mostrar decodificado", + "showRawHex": "Mostrar hex crudo" + }, + "profile": { + "notFound": "Perfil no encontrado", + "loading": "Cargando perfil...", + "invalidUrl": "URL de perfil inválida. Por favor proporcioná el tipo y el ID del perfil.", + "invalidType": "Tipo de perfil inválido: {{type}}. Debe ser uno de: network, app, organization.", + "notFoundMessage": "Perfil no encontrado: {{type}}/{{id}}", + "fetchError": "No se pudo obtener el perfil", + "unknownType": "Tipo de perfil desconocido" + }, + "rpcIndicator": { + "clickToSeeDetails": "Hacé clic para ver los detalles del proveedor RPC", + "inconsistentResponses": "Respuestas inconsistentes", + "rpcProviders": "Proveedores RPC", + "strategy": "Estrategia: {{strategy}}", + "succeededOnAttempt": "Éxito en el intento #{{count}}", + "fastestResponseWins": "Gana la respuesta más rápida • Se muestran todos los tiempos", + "responsesDiffer": "Las respuestas difieren entre proveedores", + "selected": "Seleccionado", + "winner": "Ganador", + "fallbackNote": "Modo fallback: los proveedores se prueban en secuencia", + "raceNote": "Modo carrera: se devuelve la primera respuesta exitosa. Las pendientes se descartan" + }, + "search": { + "title": "Buscar", + "resultsTitle": "Resultados de búsqueda", + "noQuery": "No se proporcionó ninguna búsqueda", + "invalidQuery": "Consulta inválida. Ingresá una dirección (0x...), hash de transacción, número de bloque o nombre ENS.", + "invalidBadge": "Inválido", + "ensInfo": "Los nombres ENS se resuelven a direcciones en Ethereum mainnet.", + "in": "en", + "badgeAddress": "Dirección", + "badgeTxHash": "Hash de TX", + "badgeBlock": "Bloque", + "badgeEns": "ENS", + "badgeUnknown": "Desconocido" + }, + "searchBox": { + "placeholder": "Buscar por dirección / hash de tx / bloque / nombre ENS", + "ariaLabel": "Buscar", + "title": "Buscar" + }, + "supporters": { + "title": "Supporters", + "subtitle": "Gracias a nuestros supporters", + "loading": "Cargando supporters...", + "error": "Error: {{error}}", + "noSupporters": "Aún no hay supporters. ¡Sé el primero!", + "becomeSupporterTitle": "Convertite en Supporter", + "becomeSupporterDescription": "Apoyá el desarrollo de OpenScan suscribiendo una red.", + "viewPlans": "Ver planes de suscripción", + "allies": "Allies", + "partners": "Partners", + "backers": "Backers" + }, + "wallet": { + "connectWallet": "Conectar wallet", + "wrongNetwork": "Red incorrecta", + "chainIcon": "Ícono de red" } } diff --git a/src/locales/es/errors.json b/src/locales/es/errors.json deleted file mode 100644 index f2872e5..0000000 --- a/src/locales/es/errors.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "failedToFetchBlock": "No se pudo obtener el bloque", - "blockNotFound": "Bloque no encontrado", - "failedToFetchTransaction": "No se pudo obtener la transacción", - "transactionNotFound": "Transacción no encontrada", - "failedToFetchAddress": "No se pudieron obtener los datos de la dirección", - "noRpcConfigured": "No hay una URL RPC configurada para la chain {{chainId}}", - "failedToFetchTransactions": "No se pudieron obtener las transacciones", - "failedToFetchTransactionHistory": "No se pudo obtener el historial de transacciones: {{error}}", - "invalidEthValue": "Valor de ETH inválido", - "errorWritingContract": "Error: {{error}}", - "errorReadingContract": "Error al leer el contrato: {{error}}", - "errorCallingContract": "Falló la llamada al contrato", - "pleaseProvideValue": "Por favor ingresá un valor para {{param}}", - "errorHeading": "Error", - "invalidProfileUrl": "URL de perfil inválida. Indicá el tipo de perfil y el ID.", - "invalidProfileType": "Tipo de perfil inválido: {{type}}. Debe ser uno de: network, app, organization.", - "profileNotFound": "Perfil no encontrado: {{type}}/{{id}}", - "failedToFetchProfile": "No se pudo obtener el perfil", - "invalidSearchQuery": "Búsqueda inválida. Ingresá una dirección (0x...), hash de transacción, número de bloque o nombre ENS.", - "noSearchQuery": "No se ingresó ningún término de búsqueda", - "ensResolutionError": "Error al resolver ENS: {{error}}", - "noMainnetRpc": "No hay un RPC de Ethereum mainnet configurado para resolver ENS", - "failedToFetchSupporters": "No se pudieron obtener los supporters" -} diff --git a/src/locales/es/home.json b/src/locales/es/home.json index b5f91a0..106fa00 100644 --- a/src/locales/es/home.json +++ b/src/locales/es/home.json @@ -1,7 +1,6 @@ { "title": "OPENSCAN", "loading": "Cargando redes...", - "networkId": "ID de Red: {{networkId}}", "showTestnets": "Mostrar testnets", "hideTestnets": "Ocultar testnets", "invalidSearchTerm": "Búsqueda inválida. Ingresá una dirección (0x...), hash de transacción, número de bloque o nombre ENS.", diff --git a/src/locales/es/settings.json b/src/locales/es/settings.json index d346faa..d69cbb6 100644 --- a/src/locales/es/settings.json +++ b/src/locales/es/settings.json @@ -78,11 +78,7 @@ "description": "Configurá las URLs RPC para cada red. Hacé click en una red para desplegar y configurar sus endpoints.", "fetchButton": "Obtener desde Chainlist", "fetchingButton": "Obteniendo...", - "placeholder": "https://eth-mainnet.g.alchemy.com/v2/TU-API-KEY", "currentRPCs": "RPCs actuales:", - "chainId": "Chain ID: {{chainId}}", - "rpcCount_one": "{{count}} RPC", - "rpcCount_other": "{{count}} RPCs", "removeRpc": "Eliminar RPC", "localhostHelp": "¿Necesitás acceder a tu red local de forma remota?", "localhostHelpLink": "Aprendé cómo configurar un túnel con ngrok", @@ -94,11 +90,5 @@ "configuredText": "¡Configurado!", "useAsDefault": "Usar como explorador por defecto" } - }, - "errors": { - "failedToFetch": "No se pudo obtener la información desde Chainlist", - "noRPCsFound": "No se encontraron RPCs para la chain {{chainId}}", - "noPrivacyFriendlyRPCs": "No se encontraron RPCs amigables con la privacidad para la chain {{chainId}}", - "errorFetchingChainlist": "Error al obtener datos desde Chainlist:" } } diff --git a/src/locales/es/tokenDetails.json b/src/locales/es/tokenDetails.json index 31ae465..9455b44 100644 --- a/src/locales/es/tokenDetails.json +++ b/src/locales/es/tokenDetails.json @@ -29,9 +29,7 @@ "checkBalance": "Consultar Balance", "balance": "Balance", "detectingTokenType": "Detectando tipo de token...", - "unknown": "desconocido", "errors": { - "missingValues": "Falta la dirección del contrato, el token ID o la URL RPC", "error": "Error" } } diff --git a/src/locales/es/transaction.json b/src/locales/es/transaction.json index 9872d47..1a357e4 100644 --- a/src/locales/es/transaction.json +++ b/src/locales/es/transaction.json @@ -6,7 +6,6 @@ "failed": "Fallida", "block": "Bloque:", "blockConfirmations": "Confirmaciones de Bloque", - "confirmations": "Confirmaciones", "timestamp": "Timestamp:", "from": "Desde:", "to": "Hacia:", @@ -25,7 +24,6 @@ "inputData": "Input Data:", "decodedInput": "Input Decodificado:", "eventLogs": "Event Logs:", - "debugTrace": "Debug Trace", "showDebugTrace": "Mostrar Debug Trace", "hideDebugTrace": "Ocultar Debug Trace", "loadingTrace": "Cargando datos del trace...", @@ -54,19 +52,6 @@ "logsData": "Data", "logsIndexed": "indexed", "logsAbi": "ABI", - "latestTransactions": "Últimas Transacciones", - "loadingTransactions": "Cargando transacciones...", - "showingFromLastBlocks": "Mostrando {{count}} transacciones de los últimos {{blocks}} bloques", - "showingFromRange": "Mostrando {{count}} transacciones de los bloques {{from}} - {{to}}", - "noTransactionsFound": "No se encontraron transacciones en el rango de bloques seleccionado", - "tableHash": "Hash Tx", - "tableBlock": "Bloque", - "tableFrom": "Desde", - "tableTo": "Hacia", - "tableValue": "Valor", - "tableGasPrice": "Gas Price", - "tableGas": "Gas", - "mempool": "Mempool", "transaction": "Transacción", "loadingTransaction": "Cargando transacción...", "transactionNotFound": "Transacción no encontrada", diff --git a/src/services/AddressTransactionSearch.ts b/src/services/AddressTransactionSearch.ts index e2f9c57..9cb528e 100644 --- a/src/services/AddressTransactionSearch.ts +++ b/src/services/AddressTransactionSearch.ts @@ -4,6 +4,7 @@ import type { EthTransactionReceipt, } from "@openscan/network-connectors"; import type { Transaction } from "../types"; +import { logger } from "../utils/logger"; import { hexToNumber } from "./adapters/EVMAdapter/utils"; interface Semaphore { @@ -738,7 +739,7 @@ export class AddressTransactionSearch { const segEndState = stateMap.get(segEnd); if (!segStartState || !segEndState) { - console.warn("[Search] Missing state for segment", i, "- skipping"); + logger.warn("[Search] Missing state for segment", i, "- skipping"); continue; } diff --git a/src/services/MetadataService.ts b/src/services/MetadataService.ts index d28561a..0a41891 100644 --- a/src/services/MetadataService.ts +++ b/src/services/MetadataService.ts @@ -4,6 +4,7 @@ */ import networksData from "../config/networks.json"; +import { logger } from "../utils/logger"; import { extractChainIdFromNetworkId } from "../utils/networkResolver"; const METADATA_BASE_URL = "https://cdn.jsdelivr.net/npm/@openscan/metadata/dist"; @@ -89,13 +90,13 @@ export async function fetchNetworkProfile(profilePath: string): Promise { return processSupporters(data); } catch (error) { - console.error("Error fetching supporters:", error); + logger.error("Error fetching supporters:", error); // Return cached data if available, even if stale if (supportersCache) { @@ -499,7 +500,7 @@ export async function fetchApps(): Promise { appsCacheTime = now; return data; } catch (error) { - console.error("Error fetching apps:", error); + logger.error("Error fetching apps:", error); if (appsCache) { return appsCache; } @@ -529,7 +530,7 @@ export async function fetchOrganizations(): Promise { orgsCacheTime = now; return data; } catch (error) { - console.error("Error fetching organizations:", error); + logger.error("Error fetching organizations:", error); if (orgsCache) { return orgsCache; } @@ -552,13 +553,13 @@ export async function fetchProfileMarkdown(profilePath: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -248,7 +249,7 @@ export class ArbitrumAdapter extends NetworkAdapter { const result = await this.client.debugTraceTransaction(txHash, {}); return extractData(result.data); } catch (error) { - console.error("Error getting transaction trace:", error); + logger.error("Error getting transaction trace:", error); return null; } } @@ -256,7 +257,7 @@ export class ArbitrumAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result async getCallTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -265,14 +266,14 @@ export class ArbitrumAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result type return extractData(result.data); } catch (error) { - console.error("Error getting call trace:", error); + logger.error("Error getting call trace:", error); return null; } } async getBlockTrace(blockHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -286,7 +287,7 @@ export class ArbitrumAdapter extends NetworkAdapter { const result = await this.client.arbtraceBlock(normalizeBlockNumber(blockNumber)); return extractData(result.data); } catch (error) { - console.error("Error getting block trace:", error); + logger.error("Error getting block trace:", error); return null; } } diff --git a/src/services/adapters/BNBAdapter/BNBAdapter.ts b/src/services/adapters/BNBAdapter/BNBAdapter.ts index 250533e..38c0923 100644 --- a/src/services/adapters/BNBAdapter/BNBAdapter.ts +++ b/src/services/adapters/BNBAdapter/BNBAdapter.ts @@ -1,6 +1,7 @@ import { type BlockNumberOrTag, NetworkAdapter } from "../NetworkAdapter"; import type { Block, Transaction, Address, NetworkStats, DataWithMetadata } from "../../../types"; import type { TraceResult } from "../NetworkAdapter"; +import { logger } from "../../../utils/logger"; import { transformBNBBlockToBlock, transformBNBTransactionToTransaction, @@ -100,7 +101,7 @@ export class BNBAdapter extends NetworkAdapter { transaction.blockBaseFeePerGas = blockResult.data.baseFeePerGas; } } catch (error) { - console.warn("Failed to fetch block for transaction timestamp:", error); + logger.warn("Failed to fetch block for transaction timestamp:", error); } } @@ -216,7 +217,7 @@ export class BNBAdapter extends NetworkAdapter { }); } } catch (error) { - console.error(`Error fetching block ${blockNum}:`, error); + logger.error(`Error fetching block ${blockNum}:`, error); } } @@ -240,7 +241,7 @@ export class BNBAdapter extends NetworkAdapter { async getTransactionTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -248,7 +249,7 @@ export class BNBAdapter extends NetworkAdapter { const result = await this.client.debugTraceTransaction(txHash, {}); return extractData(result.data); } catch (error) { - console.error("Error getting transaction trace:", error); + logger.error("Error getting transaction trace:", error); return null; } } @@ -256,7 +257,7 @@ export class BNBAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result async getCallTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -265,14 +266,14 @@ export class BNBAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result type return extractData(result.data); } catch (error) { - console.error("Error getting call trace:", error); + logger.error("Error getting call trace:", error); return null; } } async getBlockTrace(blockHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -286,7 +287,7 @@ export class BNBAdapter extends NetworkAdapter { const result = await this.client.traceBlock(blockNumber); return extractData(result.data); } catch (error) { - console.error("Error getting block trace:", error); + logger.error("Error getting block trace:", error); return null; } } diff --git a/src/services/adapters/BaseAdapter/BaseAdapter.ts b/src/services/adapters/BaseAdapter/BaseAdapter.ts index c7f0d7a..0996901 100644 --- a/src/services/adapters/BaseAdapter/BaseAdapter.ts +++ b/src/services/adapters/BaseAdapter/BaseAdapter.ts @@ -1,5 +1,6 @@ import { type BlockNumberOrTag, NetworkAdapter, type TraceResult } from "../NetworkAdapter"; import type { Block, Transaction, Address, NetworkStats, DataWithMetadata } from "../../../types"; +import { logger } from "../../../utils/logger"; import { transformBaseBlockToBlock, transformBaseTransactionToTransaction, @@ -99,7 +100,7 @@ export class BaseAdapter extends NetworkAdapter { transaction.blockBaseFeePerGas = blockResult.data.baseFeePerGas; } } catch (error) { - console.warn("Failed to fetch block for transaction timestamp:", error); + logger.warn("Failed to fetch block for transaction timestamp:", error); } } @@ -215,7 +216,7 @@ export class BaseAdapter extends NetworkAdapter { }); } } catch (error) { - console.error(`Error fetching block ${blockNum}:`, error); + logger.error(`Error fetching block ${blockNum}:`, error); } } @@ -239,7 +240,7 @@ export class BaseAdapter extends NetworkAdapter { async getTransactionTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -247,7 +248,7 @@ export class BaseAdapter extends NetworkAdapter { const result = await this.client.debugTraceTransaction(txHash, {}); return extractData(result.data); } catch (error) { - console.error("Error getting transaction trace:", error); + logger.error("Error getting transaction trace:", error); return null; } } @@ -255,7 +256,7 @@ export class BaseAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result async getCallTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -264,14 +265,14 @@ export class BaseAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result type return extractData(result.data); } catch (error) { - console.error("Error getting call trace:", error); + logger.error("Error getting call trace:", error); return null; } } async getBlockTrace(blockHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -285,7 +286,7 @@ export class BaseAdapter extends NetworkAdapter { const result = await this.client.traceBlock(blockNumber); return extractData(result.data); } catch (error) { - console.error("Error getting block trace:", error); + logger.error("Error getting block trace:", error); return null; } } diff --git a/src/services/adapters/BitcoinAdapter/BitcoinAdapter.ts b/src/services/adapters/BitcoinAdapter/BitcoinAdapter.ts index 2da15f1..3805cb4 100644 --- a/src/services/adapters/BitcoinAdapter/BitcoinAdapter.ts +++ b/src/services/adapters/BitcoinAdapter/BitcoinAdapter.ts @@ -8,6 +8,7 @@ import type { BitcoinUTXO, DataWithMetadata, } from "../../../types"; +import { logger } from "../../../utils/logger"; import { extractData } from "../shared/extractData"; /** @@ -246,7 +247,7 @@ export class BitcoinAdapter { currentHeight--; blocksToFetch++; } catch (error) { - console.error(`Error fetching Bitcoin block ${currentHeight}:`, error); + logger.error(`Error fetching Bitcoin block ${currentHeight}:`, error); currentHeight--; blocksToFetch++; } @@ -890,7 +891,7 @@ export class BitcoinAdapter { return { total: entries.length, entries }; } catch (error) { - console.error("Error fetching mempool summary:", error); + logger.error("Error fetching mempool summary:", error); return { total: 0, entries: [] }; } } @@ -961,7 +962,7 @@ export class BitcoinAdapter { return { transactions, total }; } catch (error) { - console.error("Error fetching mempool transactions:", error); + logger.error("Error fetching mempool transactions:", error); return { transactions: [], total: 0 }; } } diff --git a/src/services/adapters/EVMAdapter/EVMAdapter.ts b/src/services/adapters/EVMAdapter/EVMAdapter.ts index d0d0ae1..8b64261 100644 --- a/src/services/adapters/EVMAdapter/EVMAdapter.ts +++ b/src/services/adapters/EVMAdapter/EVMAdapter.ts @@ -1,6 +1,7 @@ import { type BlockNumberOrTag, NetworkAdapter } from "../NetworkAdapter"; import type { Block, Transaction, Address, NetworkStats, DataWithMetadata } from "../../../types"; import type { TraceResult } from "../NetworkAdapter"; +import { logger } from "../../../utils/logger"; import { transformRPCBlockToBlock, transformRPCTransactionToTransaction, @@ -98,7 +99,7 @@ export class EVMAdapter extends NetworkAdapter { transaction.blockBaseFeePerGas = blockResult.data.baseFeePerGas; } } catch (error) { - console.warn("Failed to fetch block for transaction timestamp:", error); + logger.warn("Failed to fetch block for transaction timestamp:", error); } } @@ -214,7 +215,7 @@ export class EVMAdapter extends NetworkAdapter { }); } } catch (error) { - console.error(`Error fetching block ${blockNum}:`, error); + logger.error(`Error fetching block ${blockNum}:`, error); } } @@ -238,7 +239,7 @@ export class EVMAdapter extends NetworkAdapter { async getTransactionTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -246,7 +247,7 @@ export class EVMAdapter extends NetworkAdapter { const result = await this.client.debugTraceTransaction(txHash, {}); return extractData(result.data); } catch (error) { - console.error("Error getting transaction trace:", error); + logger.error("Error getting transaction trace:", error); return null; } } @@ -254,7 +255,7 @@ export class EVMAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result async getCallTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -263,14 +264,14 @@ export class EVMAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result type return extractData(result.data); } catch (error) { - console.error("Error getting call trace:", error); + logger.error("Error getting call trace:", error); return null; } } async getBlockTrace(blockHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -284,7 +285,7 @@ export class EVMAdapter extends NetworkAdapter { const result = await this.client.traceBlock(blockNumber); return extractData(result.data); } catch (error) { - console.error("Error getting block trace:", error); + logger.error("Error getting block trace:", error); return null; } } diff --git a/src/services/adapters/NetworkAdapter.ts b/src/services/adapters/NetworkAdapter.ts index a1cd507..ac0fe2c 100644 --- a/src/services/adapters/NetworkAdapter.ts +++ b/src/services/adapters/NetworkAdapter.ts @@ -8,6 +8,7 @@ import type { AddressTransactionsResult, GasPrices, } from "../../types"; +import { logger } from "../../utils/logger"; import { extractData } from "./shared/extractData"; import { AddressTransactionSearch } from "../AddressTransactionSearch"; @@ -170,7 +171,7 @@ export abstract class NetworkAdapter { message: undefined, }; } catch (error) { - console.error("Error searching address transactions:", error); + logger.error("Error searching address transactions:", error); return { transactions: [], diff --git a/src/services/adapters/OptimismAdapter/OptimismAdapter.ts b/src/services/adapters/OptimismAdapter/OptimismAdapter.ts index 5f32631..7bd9fbf 100644 --- a/src/services/adapters/OptimismAdapter/OptimismAdapter.ts +++ b/src/services/adapters/OptimismAdapter/OptimismAdapter.ts @@ -1,5 +1,6 @@ import { type BlockNumberOrTag, NetworkAdapter, type TraceResult } from "../NetworkAdapter"; import type { Block, Transaction, Address, NetworkStats, DataWithMetadata } from "../../../types"; +import { logger } from "../../../utils/logger"; import { transformOptimismBlockToBlock, transformOptimismTransactionToTransaction, @@ -99,7 +100,7 @@ export class OptimismAdapter extends NetworkAdapter { transaction.blockBaseFeePerGas = blockResult.data.baseFeePerGas; } } catch (error) { - console.warn("Failed to fetch block for transaction timestamp:", error); + logger.warn("Failed to fetch block for transaction timestamp:", error); } } @@ -215,7 +216,7 @@ export class OptimismAdapter extends NetworkAdapter { }); } } catch (error) { - console.error(`Error fetching block ${blockNum}:`, error); + logger.error(`Error fetching block ${blockNum}:`, error); } } @@ -239,7 +240,7 @@ export class OptimismAdapter extends NetworkAdapter { async getTransactionTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -247,7 +248,7 @@ export class OptimismAdapter extends NetworkAdapter { const result = await this.client.debugTraceTransaction(txHash, {}); return extractData(result.data); } catch (error) { - console.error("Error getting transaction trace:", error); + logger.error("Error getting transaction trace:", error); return null; } } @@ -255,7 +256,7 @@ export class OptimismAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result async getCallTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -264,14 +265,14 @@ export class OptimismAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result type return extractData(result.data); } catch (error) { - console.error("Error getting call trace:", error); + logger.error("Error getting call trace:", error); return null; } } async getBlockTrace(blockHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -285,7 +286,7 @@ export class OptimismAdapter extends NetworkAdapter { const result = await this.client.traceBlock(blockNumber); return extractData(result.data); } catch (error) { - console.error("Error getting block trace:", error); + logger.error("Error getting block trace:", error); return null; } } diff --git a/src/services/adapters/PolygonAdapter/PolygonAdapter.ts b/src/services/adapters/PolygonAdapter/PolygonAdapter.ts index 7aecf60..e1f634c 100644 --- a/src/services/adapters/PolygonAdapter/PolygonAdapter.ts +++ b/src/services/adapters/PolygonAdapter/PolygonAdapter.ts @@ -1,6 +1,7 @@ import { type BlockNumberOrTag, NetworkAdapter } from "../NetworkAdapter"; import type { Block, Transaction, Address, NetworkStats, DataWithMetadata } from "../../../types"; import type { TraceResult } from "../NetworkAdapter"; +import { logger } from "../../../utils/logger"; import { transformPolygonBlockToBlock, transformPolygonTransactionToTransaction, @@ -99,7 +100,7 @@ export class PolygonAdapter extends NetworkAdapter { transaction.blockBaseFeePerGas = blockResult.data.baseFeePerGas; } } catch (error) { - console.warn("Failed to fetch block for transaction timestamp:", error); + logger.warn("Failed to fetch block for transaction timestamp:", error); } } @@ -215,7 +216,7 @@ export class PolygonAdapter extends NetworkAdapter { }); } } catch (error) { - console.error(`Error fetching block ${blockNum}:`, error); + logger.error(`Error fetching block ${blockNum}:`, error); } } @@ -239,7 +240,7 @@ export class PolygonAdapter extends NetworkAdapter { async getTransactionTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -247,7 +248,7 @@ export class PolygonAdapter extends NetworkAdapter { const result = await this.client.debugTraceTransaction(txHash, {}); return extractData(result.data); } catch (error) { - console.error("Error getting transaction trace:", error); + logger.error("Error getting transaction trace:", error); return null; } } @@ -255,7 +256,7 @@ export class PolygonAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result async getCallTrace(txHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -264,14 +265,14 @@ export class PolygonAdapter extends NetworkAdapter { // biome-ignore lint/suspicious/noExplicitAny: Generic trace result type return extractData(result.data); } catch (error) { - console.error("Error getting call trace:", error); + logger.error("Error getting call trace:", error); return null; } } async getBlockTrace(blockHash: string): Promise { if (!this.isLocalHost) { - console.warn("Trace methods are only available on localhost networks"); + logger.warn("Trace methods are only available on localhost networks"); return null; } @@ -285,7 +286,7 @@ export class PolygonAdapter extends NetworkAdapter { const result = await this.client.traceBlock(blockNumber); return extractData(result.data); } catch (error) { - console.error("Error getting block trace:", error); + logger.error("Error getting block trace:", error); return null; } } diff --git a/src/utils/artifactsStorage.ts b/src/utils/artifactsStorage.ts index 11d9a16..9e0b8ab 100644 --- a/src/utils/artifactsStorage.ts +++ b/src/utils/artifactsStorage.ts @@ -1,3 +1,5 @@ +import { logger } from "./logger"; + const STORAGE_KEY = "OPENSCAN_ARTIFACTS_JSON_V1"; // biome-ignore lint/suspicious/noExplicitAny: @@ -28,7 +30,7 @@ export function loadJsonFilesFromStorage(): JsonFilesMap { if (!isValidJsonFilesMap(parsed)) return {}; return parsed; } catch (err) { - console.warn("Failed to parse JSON files from storage", err); + logger.warn("Failed to parse JSON files from storage", err); return {}; } } @@ -40,10 +42,10 @@ export function saveJsonFilesToStorage(jsonFiles: JsonFilesMap): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(jsonFiles)); } catch (err) { - console.warn("Failed to save JSON files to storage", err); + logger.warn("Failed to save JSON files to storage", err); // Handle quota exceeded errors if (err instanceof Error && err.name === "QuotaExceededError") { - console.error("LocalStorage quota exceeded. Consider clearing old artifacts."); + logger.error("LocalStorage quota exceeded. Consider clearing old artifacts."); } } } @@ -55,7 +57,7 @@ export function clearJsonFilesFromStorage(): void { try { localStorage.removeItem(STORAGE_KEY); } catch (err) { - console.warn("Failed to clear JSON files from storage", err); + logger.warn("Failed to clear JSON files from storage", err); } } diff --git a/src/utils/devArtifacts.ts b/src/utils/devArtifacts.ts index 8d375e2..4deb2d6 100644 --- a/src/utils/devArtifacts.ts +++ b/src/utils/devArtifacts.ts @@ -1,4 +1,5 @@ import { STORAGE_KEY } from "./artifactsStorage"; +import { logger } from "./logger"; declare global { const __DEV_ARTIFACTS__: Record | null; @@ -28,7 +29,7 @@ export function injectDevArtifacts(): void { try { const parsed = JSON.parse(existing); if (Object.keys(parsed).length > 0) { - console.log("[dev-artifacts] localStorage already has artifacts, skipping injection"); + logger.debug("[dev-artifacts] localStorage already has artifacts, skipping injection"); return; } } catch { @@ -38,10 +39,10 @@ export function injectDevArtifacts(): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(__DEV_ARTIFACTS__)); - console.log( + logger.debug( `[dev-artifacts] Injected ${Object.keys(__DEV_ARTIFACTS__).length} contract artifacts into localStorage`, ); } catch (err) { - console.warn("[dev-artifacts] Failed to inject artifacts into localStorage:", err); + logger.warn("[dev-artifacts] Failed to inject artifacts into localStorage:", err); } } diff --git a/src/utils/erc1155Metadata.ts b/src/utils/erc1155Metadata.ts index 88dcddf..4fc1883 100644 --- a/src/utils/erc1155Metadata.ts +++ b/src/utils/erc1155Metadata.ts @@ -4,6 +4,7 @@ */ import { decodeAbiString, hexToString } from "./hexUtils"; +import { logger } from "./logger"; export interface ERC1155TokenMetadata { name?: string; @@ -62,7 +63,7 @@ function parseDataUri(uri: string): ERC1155TokenMetadata | null { } return null; } catch (error) { - console.error("Failed to parse data URI:", error); + logger.error("Failed to parse data URI:", error); return null; } } @@ -113,7 +114,7 @@ export async function fetchTokenUri( return uri; } catch (error) { - console.error("Failed to fetch token URI:", error); + logger.error("Failed to fetch token URI:", error); return null; } } @@ -155,7 +156,7 @@ export async function fetchTokenBalance( const balance = BigInt(result.result).toString(); return balance; } catch (error) { - console.error("Failed to fetch token balance:", error); + logger.error("Failed to fetch token balance:", error); return null; } } @@ -207,14 +208,14 @@ export async function fetchERC1155MetadataWithUri( try { const response = await fetch(httpUri); if (!response.ok) { - console.error("Failed to fetch metadata:", response.status); + logger.error("Failed to fetch metadata:", response.status); return { metadata: null, tokenUri: uri }; } const metadata = await response.json(); return { metadata: metadata as ERC1155TokenMetadata, tokenUri: uri }; } catch (error) { - console.error("Failed to fetch/parse metadata:", error); + logger.error("Failed to fetch/parse metadata:", error); return { metadata: null, tokenUri: uri }; } } diff --git a/src/utils/erc721Metadata.ts b/src/utils/erc721Metadata.ts index 337a62f..f51590d 100644 --- a/src/utils/erc721Metadata.ts +++ b/src/utils/erc721Metadata.ts @@ -4,6 +4,7 @@ */ import { decodeAbiString, hexToString } from "./hexUtils"; +import { logger } from "./logger"; export interface ERC721TokenMetadata { name?: string; @@ -51,7 +52,7 @@ function parseDataUri(uri: string): ERC721TokenMetadata | null { } return null; } catch (error) { - console.error("Failed to parse data URI:", error); + logger.error("Failed to parse data URI:", error); return null; } } @@ -102,7 +103,7 @@ export async function fetchTokenUri( return uri; } catch (error) { - console.error("Failed to fetch token URI:", error); + logger.error("Failed to fetch token URI:", error); return null; } } @@ -146,7 +147,7 @@ export async function fetchTokenOwner( const address = `0x${hex.slice(-40)}`; return address; } catch (error) { - console.error("Failed to fetch token owner:", error); + logger.error("Failed to fetch token owner:", error); return null; } } @@ -194,7 +195,7 @@ export async function fetchTokenApproval( } return address; } catch (error) { - console.error("Failed to fetch token approval:", error); + logger.error("Failed to fetch token approval:", error); return null; } } @@ -243,14 +244,14 @@ export async function fetchERC721MetadataWithUri( try { const response = await fetch(httpUri); if (!response.ok) { - console.error("Failed to fetch metadata:", response.status); + logger.error("Failed to fetch metadata:", response.status); return { metadata: null, tokenUri: uri }; } const metadata = await response.json(); return { metadata: metadata as ERC721TokenMetadata, tokenUri: uri }; } catch (error) { - console.error("Failed to fetch/parse metadata:", error); + logger.error("Failed to fetch/parse metadata:", error); return { metadata: null, tokenUri: uri }; } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 085a027..eb82e5f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,3 +2,4 @@ export * from "./constants"; export * from "./networkConfig"; export * from "./web3Security"; +export { logger } from "./logger"; diff --git a/src/utils/logger.test.ts b/src/utils/logger.test.ts new file mode 100644 index 0000000..47217d1 --- /dev/null +++ b/src/utils/logger.test.ts @@ -0,0 +1,115 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Logger", () => { + let consoleLogSpy: ReturnType; + let consoleWarnSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.resetModules(); + }); + + describe("in development environment", () => { + beforeEach(() => { + vi.doMock("./constants", () => ({ ENVIRONMENT: "development" })); + }); + + it("should log debug messages", async () => { + const { logger } = await import("./logger"); + logger.debug("test debug"); + expect(consoleLogSpy).toHaveBeenCalledWith("[DEBUG]", "test debug"); + }); + + it("should log info messages", async () => { + const { logger } = await import("./logger"); + logger.info("test info"); + expect(consoleLogSpy).toHaveBeenCalledWith("[INFO]", "test info"); + }); + + it("should log warn messages", async () => { + const { logger } = await import("./logger"); + logger.warn("test warn"); + expect(consoleWarnSpy).toHaveBeenCalledWith("[WARN]", "test warn"); + }); + + it("should log error messages", async () => { + const { logger } = await import("./logger"); + logger.error("test error"); + expect(consoleErrorSpy).toHaveBeenCalledWith("[ERROR]", "test error"); + }); + + it("should pass multiple arguments", async () => { + const { logger } = await import("./logger"); + const testObj = { key: "value" }; + logger.debug("message", testObj, 123); + expect(consoleLogSpy).toHaveBeenCalledWith("[DEBUG]", "message", testObj, 123); + }); + }); + + describe("in staging environment", () => { + beforeEach(() => { + vi.doMock("./constants", () => ({ ENVIRONMENT: "staging" })); + }); + + it("should NOT log debug messages", async () => { + const { logger } = await import("./logger"); + logger.debug("test debug"); + expect(consoleLogSpy).not.toHaveBeenCalled(); + }); + + it("should log info messages", async () => { + const { logger } = await import("./logger"); + logger.info("test info"); + expect(consoleLogSpy).toHaveBeenCalledWith("[INFO]", "test info"); + }); + + it("should log warn messages", async () => { + const { logger } = await import("./logger"); + logger.warn("test warn"); + expect(consoleWarnSpy).toHaveBeenCalledWith("[WARN]", "test warn"); + }); + + it("should log error messages", async () => { + const { logger } = await import("./logger"); + logger.error("test error"); + expect(consoleErrorSpy).toHaveBeenCalledWith("[ERROR]", "test error"); + }); + }); + + describe("in production environment", () => { + beforeEach(() => { + vi.doMock("./constants", () => ({ ENVIRONMENT: "production" })); + }); + + it("should NOT log debug messages", async () => { + const { logger } = await import("./logger"); + logger.debug("test debug"); + expect(consoleLogSpy).not.toHaveBeenCalled(); + }); + + it("should NOT log info messages", async () => { + const { logger } = await import("./logger"); + logger.info("test info"); + expect(consoleLogSpy).not.toHaveBeenCalled(); + }); + + it("should log warn messages", async () => { + const { logger } = await import("./logger"); + logger.warn("test warn"); + expect(consoleWarnSpy).toHaveBeenCalledWith("[WARN]", "test warn"); + }); + + it("should log error messages", async () => { + const { logger } = await import("./logger"); + logger.error("test error"); + expect(consoleErrorSpy).toHaveBeenCalledWith("[ERROR]", "test error"); + }); + }); +}); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..e95c7fd --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,33 @@ +import { ENVIRONMENT } from "./constants"; + +type LogLevel = "debug" | "info" | "warn" | "error"; + +const LOG_LEVELS: Record = { + debug: 0, + info: 1, + warn: 2, + error: 3, +}; + +const MIN_LOG_LEVEL: Record = { + development: "debug", + staging: "info", + production: "warn", +}; + +const minLevel = LOG_LEVELS[MIN_LOG_LEVEL[ENVIRONMENT] || "debug"]; + +export const logger = { + debug: (...args: unknown[]): void => { + if (LOG_LEVELS.debug >= minLevel) console.log("[DEBUG]", ...args); + }, + info: (...args: unknown[]): void => { + if (LOG_LEVELS.info >= minLevel) console.log("[INFO]", ...args); + }, + warn: (...args: unknown[]): void => { + if (LOG_LEVELS.warn >= minLevel) console.warn("[WARN]", ...args); + }, + error: (...args: unknown[]): void => { + if (LOG_LEVELS.error >= minLevel) console.error("[ERROR]", ...args); + }, +}; diff --git a/src/utils/rpcStorage.ts b/src/utils/rpcStorage.ts index 18a8ff3..b14b8a2 100644 --- a/src/utils/rpcStorage.ts +++ b/src/utils/rpcStorage.ts @@ -1,5 +1,6 @@ import { getAllNetworks } from "../config/networks"; import type { RpcUrlsContextType } from "../types"; +import { logger } from "./logger"; import { getNetworkRpcKey } from "./networkResolver"; const STORAGE_KEY = "OPENSCAN_RPC_URLS_V3"; // Version bump for networkId-based keys @@ -44,7 +45,7 @@ export function loadRpcUrlsFromStorage(): RpcUrlsContextType | null { if (!isValidRpcMap(parsed)) return null; return parsed; } catch (err) { - console.warn("Failed to parse RPC urls from storage", err); + logger.warn("Failed to parse RPC urls from storage", err); return null; } } @@ -57,7 +58,7 @@ export function saveRpcUrlsToStorage(map: RpcUrlsContextType): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(map)); } catch (err) { - console.warn("Failed to save RPC urls to storage", err); + logger.warn("Failed to save RPC urls to storage", err); } } diff --git a/src/utils/web3Security.ts b/src/utils/web3Security.ts index d707f58..3f34952 100644 --- a/src/utils/web3Security.ts +++ b/src/utils/web3Security.ts @@ -1,4 +1,5 @@ import { ethers } from "ethers"; +import { logger } from "./logger"; /** * Security utilities for Web3 provider interactions @@ -112,7 +113,7 @@ export async function createSecureSigner(): Promise { } if (security.warnings.length > 0) { - console.warn("Web3 Security Warnings:", security.warnings); + logger.warn("Web3 Security Warnings:", security.warnings); } if (!window.ethereum) { @@ -140,10 +141,10 @@ export async function createSecureSigner(): Promise { throw new Error("Invalid signer address received"); } - console.log("✅ Secure signer created:", address); + logger.debug("Secure signer created:", address); return signer; } catch (error) { - console.error("❌ Error creating secure signer:", error); + logger.error("Error creating secure signer:", error); throw new Error( `Failed to create secure signer: ${error instanceof Error ? error.message : "Unknown error"}`, ); diff --git a/vite.config.ts b/vite.config.ts index 43aded7..c836063 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -40,6 +40,7 @@ export default defineConfig({ compress: { drop_console: false, drop_debugger: false, + pure_funcs: process.env.NODE_ENV === "production" ? ["console.log"] : [], }, }, },