From ef94f4280c7a8323788be2be15bd805559d0779e Mon Sep 17 00:00:00 2001 From: krandder Date: Sun, 5 Jul 2026 23:23:03 +0200 Subject: [PATCH 1/2] Remove remaining Supabase clients and drop the dependency - Delete RecentTradesDataLayer.jsx (never rendered in prod: ENABLE_SUBGRAPH_FOR_ALL_PROPOSALS forces the subgraph path) - Strip Supabase client + candle/trade fetch paths from MarketPageShowcaseViewModel and MarketHistoryViewModel (subgraph/SQL-direct paths kept) - Remove @supabase/supabase-js from package.json and lockfile Netlify prod env has no Supabase vars, so all removed code was dead weight. scripts/generate-seo.mjs still references Supabase but only runs via the optional build-with-seo script; replacing it with a registry-fed generator is tracked separately. --- package-lock.json | 96 -- package.json | 1 - .../marketPage/MarketHistoryViewModel.jsx | 492 +------- .../marketPage/MarketPageShowcase.jsx | 43 +- .../MarketPageShowcaseViewModel.jsx | 183 +-- .../marketPage/RecentTradesDataLayer.jsx | 1002 ----------------- 6 files changed, 32 insertions(+), 1785 deletions(-) delete mode 100644 src/components/futarchyFi/marketPage/RecentTradesDataLayer.jsx diff --git a/package-lock.json b/package-lock.json index 92c3833..b1f0aa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@safe-global/protocol-kit": "^6.1.2", "@safe-global/safe-core-sdk-types": "^5.1.0", "@storybook/addon-actions": "^8.1.10", - "@supabase/supabase-js": "^2.97.0", "@swapr/sdk": "github:rodsouto/swapr-sdk#5e3b4f7abf8411434f0a05628a23653c8d6b87a0", "@tanstack/react-query": "^5.62.16", "@uniswap/universal-router-sdk": "^4.32.0", @@ -9155,86 +9154,6 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, - "node_modules/@supabase/auth-js": { - "version": "2.97.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.97.0.tgz", - "integrity": "sha512-2Og/1lqp+AIavr8qS2X04aSl8RBY06y4LrtIAGxat06XoXYiDxKNQMQzWDAKm1EyZFZVRNH48DO5YvIZ7la5fQ==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.97.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.97.0.tgz", - "integrity": "sha512-fSaA0ZeBUS9hMgpGZt5shIZvfs3Mvx2ZdajQT4kv/whubqDBAp3GU5W8iIXy21MRvKmO2NpAj8/Q6y+ZkZyF/w==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.97.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.97.0.tgz", - "integrity": "sha512-g4Ps0eaxZZurvfv/KGoo2XPZNpyNtjth9aW8eho9LZWM0bUuBtxPZw3ZQ6ERSpEGogshR+XNgwlSPIwcuHCNww==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.97.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.97.0.tgz", - "integrity": "sha512-37Jw0NLaFP0CZd7qCan97D1zWutPrTSpgWxAw6Yok59JZoxp4IIKMrPeftJ3LZHmf+ILQOPy3i0pRDHM9FY36Q==", - "license": "MIT", - "dependencies": { - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "tslib": "2.8.1", - "ws": "^8.18.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.97.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.97.0.tgz", - "integrity": "sha512-9f6NniSBfuMxOWKwEFb+RjJzkfMdJUwv9oHuFJKfe/5VJR8cd90qw68m6Hn0ImGtwG37TUO+QHtoOechxRJ1Yg==", - "license": "MIT", - "dependencies": { - "iceberg-js": "^0.8.1", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.97.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.97.0.tgz", - "integrity": "sha512-kTD91rZNO4LvRUHv4x3/4hNmsEd2ofkYhuba2VMUPRVef1RCmnHtm7rIws38Fg0yQnOSZOplQzafn0GSiy6GVg==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.97.0", - "@supabase/functions-js": "2.97.0", - "@supabase/postgrest-js": "2.97.0", - "@supabase/realtime-js": "2.97.0", - "@supabase/storage-js": "2.97.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@swapr/core": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/@swapr/core/-/core-0.3.20.tgz", @@ -9741,12 +9660,6 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, - "node_modules/@types/phoenix": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", - "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -20324,15 +20237,6 @@ "ms": "^2.0.0" } }, - "node_modules/iceberg-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index 472171c..bf2fc7b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "@safe-global/protocol-kit": "^6.1.2", "@safe-global/safe-core-sdk-types": "^5.1.0", "@storybook/addon-actions": "^8.1.10", - "@supabase/supabase-js": "^2.97.0", "@swapr/sdk": "github:rodsouto/swapr-sdk#5e3b4f7abf8411434f0a05628a23653c8d6b87a0", "@tanstack/react-query": "^5.62.16", "@uniswap/universal-router-sdk": "^4.32.0", diff --git a/src/components/futarchyFi/marketPage/MarketHistoryViewModel.jsx b/src/components/futarchyFi/marketPage/MarketHistoryViewModel.jsx index f4dd5ab..fa239d9 100644 --- a/src/components/futarchyFi/marketPage/MarketHistoryViewModel.jsx +++ b/src/components/futarchyFi/marketPage/MarketHistoryViewModel.jsx @@ -2,31 +2,12 @@ import React from 'react'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; -import { useAccount } from 'wagmi'; import { ethers } from 'ethers'; -import { createClient } from '@supabase/supabase-js'; // Cascade: Added Supabase client import -import { useContractConfig } from '../../../hooks/useContractConfig'; // Import contract config hook // Extend dayjs with plugins dayjs.extend(utc); dayjs.extend(timezone); -// --- Cascade: Initialize Supabase client --- -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; -const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; - -let supabase; -if (supabaseUrl && supabaseAnonKey) { - supabase = createClient(supabaseUrl, supabaseAnonKey); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { // Assuming 'false' means debug is ON based on user's previous changes - console.log('DEBUG (ViewModel): Supabase client initialized successfully.'); - } -} else { - console.error('Supabase URL or Anon Key is missing. Supabase client NOT initialized. Ensure NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are set in your environment.'); - // Set supabase to a null object or a mock that prevents further errors if you want to handle this gracefully - // For now, functions using supabase will check if it's defined. -} -// --- End Cascade Supabase initialization --- // List of reliable Gnosis Chain RPC endpoints (same as other utilities) const GNOSIS_RPCS = [ @@ -218,63 +199,8 @@ function populateTokenCacheFromConfig(config) { console.log(`[TRADE_HISTORY_DEBUG] 🚀 INIT: Pre-warmed token cache with ${Object.keys(COMMON_TOKENS).length} common tokens:`, COMMON_TOKENS); -// --- Cascade: Added useSQLDirectMode flag and new Supabase functions --- -const useSQLDirectMode = true; // Set to false to use the API_ENDPOINT for trade history -const realtime_websocket = true; // Flag to enable WebSocket real-time updates -const polling_mode = true; // Flag to enable polling mode (fetches all trades every 10 seconds) -const POLLING_INTERVAL = 10000; // 10 seconds in milliseconds - -// Placeholder for supabase client if not imported. -// Ensure 'supabase' is correctly initialized and available in this scope. -// Example: const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY); - -async function fetchCandles(pool_interval_id) { - if (!supabase) { - console.error('Supabase client is not available. Cannot fetch candles.'); - return null; - } - const { data, error } = await supabase - .from('pool_candles') - .select('*') - .eq('pool_interval_id', pool_interval_id) - .order('timestamp', { ascending: true }); - - if (error) { - console.error('Error fetching candles from Supabase:', error); - return null; - } - - return data; -} - -async function fetchTradeHistoryFromSupabase(user_address) { - if (!supabase) { - console.error('Supabase client is not available. Cannot fetch trade history.'); - return null; - } - const { data, error } = await supabase - .from('trade_history') - .select('*') - .eq('user_address', user_address.toLowerCase()) - .order('evt_block_time', { ascending: false }); - - if (error) { - console.error('Error fetching trade history from Supabase:', error); - return null; - } - return data; -} -// --- End Cascade changes --- -// Module-level cache setup -const tradeHistoryCache = new Map(); -const activeFetchPromises = new Map(); // To track active fetches by address -const CACHE_TTL = 600000; // 10 minutes -let fetchOperationIdCounter = 0; // Counter for unique fetch operation IDs -// Constants from getTradeHistory.js -const BEARER_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im52aHFkcXRsc2Rib2N0cWpjZWxxIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDIxNDU3ODEsImV4cCI6MjA1NzcyMTc4MX0.6kjpxGVqSQNMz3DqycuNPv_ug8sdBNKeJsN0Z3X7oLg'; -const API_ENDPOINT = 'https://nvhqdqtlsdboctqjcelq.supabase.co/functions/v1/Trade-History'; const BASE_COMPANY_SYMBOL = 'GNO'; // Default fallback symbol const DEFAULT_CURRENCY_SYMBOL = 'sDAI'; @@ -826,415 +752,15 @@ async function processAndFormatTrade(enrichedTrade, queriedPoolAddress) { return formattedTrade; } -export function useTradeHistory(config = null) { - const { address, isConnected } = useAccount(); - - // Get contract config to populate token cache - const { config: contractConfig, loading: configLoading } = useContractConfig(); - - // Use contract config if available, otherwise fall back to passed config - const effectiveConfig = contractConfig || config; - - const [trades, setTrades] = React.useState([]); - const [loading, setLoading] = React.useState(true); - const [error, setError] = React.useState(null); - // Generate a unique ID for this hook instance's fetch operations for clearer logging - const [currentFetchOperationId] = React.useState(() => `fetch-${fetchOperationIdCounter++}`); - - // Prevent excessive re-initialization - const [isInitialized, setIsInitialized] = React.useState(false); - const processingLock = React.useRef(false); - const lastProcessedAddress = React.useRef(null); - - // Log contract config status for guarantee - React.useEffect(() => { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 CONTRACT CONFIG STATUS:`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 - configLoading: ${configLoading}`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 - contractConfig available: ${!!contractConfig}`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 - passed config available: ${!!config}`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 - effectiveConfig source: ${contractConfig ? 'CONTRACT_CONFIG' : (config ? 'PASSED_CONFIG' : 'NONE')}`); - - if (contractConfig) { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 - Contract config details:`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 * BASE_TOKENS_CONFIG: ${!!contractConfig.BASE_TOKENS_CONFIG}`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 * MERGE_CONFIG: ${!!contractConfig.MERGE_CONFIG}`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 * proposalId: ${contractConfig.proposalId}`); - } - }, [contractConfig, configLoading, config, currentFetchOperationId]); - - // Populate token cache when contract config is available - React.useEffect(() => { - if (effectiveConfig && !configLoading) { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 POPULATING TOKEN CACHE from ${contractConfig ? 'CONTRACT_CONFIG' : 'PASSED_CONFIG'}`); - populateTokenCacheFromConfig(effectiveConfig); - } else { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔧 SKIPPING TOKEN CACHE POPULATION - effectiveConfig: ${!!effectiveConfig}, configLoading: ${configLoading}`); - } - }, [effectiveConfig, configLoading, contractConfig, currentFetchOperationId]); - - // --- Cascade: Added console log for useSQLDirectMode status --- - React.useEffect(() => { - if (!isInitialized) { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🚀 HOOK INIT: useTradeHistory initialized. useSQLDirectMode is ${useSQLDirectMode ? 'ENABLED' : 'DISABLED'}. Realtime WebSocket is ${realtime_websocket ? 'ENABLED' : 'DISABLED'}. Polling Mode is ${polling_mode ? 'ENABLED' : 'DISABLED'}.`); - setIsInitialized(true); - } else { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] ⚠️ HOOK RE-INIT: Hook already initialized but function called again!`); - } - }, [currentFetchOperationId, isInitialized]); - // --- End Cascade changes --- - - const fetchTrades = React.useCallback(async (forceRefresh = false) => { - let isMounted = true; // Simulating isMounted for async operations within useCallback - - // Prevent duplicate processing - if (processingLock.current && !forceRefresh) { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔒 LOCK: fetchTrades already in progress, skipping duplicate call`); - return trades; // Return current trades instead of processing again - } - - // Add more detailed debugging - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 📞 FETCH: fetchTrades called with address: ${address}, isConnected: ${isConnected}, forceRefresh: ${forceRefresh}`); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 📊 STATE: Current trades state has ${trades.length} trades`); - - if (!address) { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] ❌ ADDR: No user address, returning empty array for fetchTrades.`); - setTrades([]); // Clear trades if no address - return []; - } - - // Set processing lock - processingLock.current = true; - lastProcessedAddress.current = address; - const overallStartTime = performance.now(); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] ⏱️ START: Beginning fetch pipeline at ${overallStartTime.toFixed(2)}ms`); - - // If not forcing refresh, check for an active promise first - if (!forceRefresh && activeFetchPromises.has(address)) { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG: Active fetch already in progress for ${address}. Returning existing promise.`); - return activeFetchPromises.get(address); - } - - // If not forcing refresh, check cache next - if (!forceRefresh) { - const cachedEntry = tradeHistoryCache.get(address); - if (cachedEntry && (Date.now() - cachedEntry.timestamp < CACHE_TTL)) { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Cache hit! Returning ${cachedEntry.trades.length} cached trades for address ${address}.`); - setTrades(cachedEntry.trades); // Update state with cached trades - return cachedEntry.trades; - } else { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Cache miss or expired for address ${address}. Cached entry: ${!!cachedEntry}, age: ${cachedEntry ? (Date.now() - cachedEntry.timestamp) : 'N/A'}ms`); - } - } - - const currentFetchId = ++fetchOperationIdCounter; - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Starting new fetch operation [FetchID ${currentFetchId}] for address ${address}.`); - - // Proceed to fetch (or re-fetch if forceRefresh is true) - const fetchPromise = (async () => { - try { - if (forceRefresh && process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG: [FetchID ${currentFetchId}] Force refresh requested for ${address}. Bypassing cache.`); - } else if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG: [FetchID ${currentFetchId}] Cache miss or expired for ${address}. Fetching fresh trades.`); - } - - let allTrades = []; - - let fetchPromise; - if (useSQLDirectMode) { - // --- Cascade: Use fetchTradeHistoryFromSupabase if useSQLDirectMode is true --- - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🗄️ SQL: SQL Direct Mode ON. Preparing to fetch trades for ${address} from Supabase via fetchTradeHistoryFromSupabase.`); - const networkStartTime = performance.now(); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🌐 NETWORK: Starting Supabase fetch at ${networkStartTime.toFixed(2)}ms`); - - fetchPromise = fetchTradeHistoryFromSupabase(address) - .then(async (directTrades) => { - const networkEndTime = performance.now(); - const networkTime = networkEndTime - networkStartTime; - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🌐 NETWORK: fetchTradeHistoryFromSupabase returned ${directTrades ? directTrades.length : 0} raw trades for ${address}. Network time: ${networkTime.toFixed(2)}ms`); - if (!directTrades) { - console.error(`Error fetching trades from Supabase for address ${address}: No data returned.`); - return []; - } - const enrichStartTime = performance.now(); - const enrichedTrades = await enrichTradeData(directTrades, address, effectiveConfig); - const enrichEndTime = performance.now(); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔄 ENRICH: enrichTradeData returned ${enrichedTrades.length} enriched trades. Time: ${(enrichEndTime - enrichStartTime).toFixed(2)}ms`); - - const processStartTime = performance.now(); - const processedTrades = await Promise.all(enrichedTrades.map(trade => processAndFormatTrade(trade, address))); - const processEndTime = performance.now(); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] ⚙️ PROCESS: processAndFormatTrade completed for ${processedTrades.length} trades. Time: ${(processEndTime - processStartTime).toFixed(2)}ms`); - allTrades.push(...processedTrades); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] About to return ${allTrades.length} trades from SQL Direct Mode.`); - return allTrades; - }); - // --- End Cascade changes --- - } else { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: SQL Direct Mode OFF. Preparing to fetch trades for ${address} via API_ENDPOINT.`); - // Existing logic using API_ENDPOINT with dynamic pools - const poolsToQuery = getPoolsToQuery(effectiveConfig); - fetchPromise = Promise.all(poolsToQuery.map(async (pool) => { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Querying pool ${pool.name} (${pool.address}) for user ${address} via API.`); - // Keep this log always active as per user request - console.log(`DEBUG: [FetchID ${currentFetchId}] ====> MAKING HTTP POST to ${API_ENDPOINT} for pool_address: ${pool.address}, user_address: ${address}`); - - const response = await fetch(API_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${BEARER_TOKEN}`, - }, - body: JSON.stringify({ - pool_address: pool.address, - user_address: address, - }), - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error(`Error fetching trades for pool ${pool.address}: ${response.status} - ${errorText}`); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG: Failed response for pool ${pool.address}:`, errorText); - return []; // Continue to next pool on error - } - - const responseData = await response.json(); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG: Full response from pool ${pool.address}:`, responseData); - - if (responseData && responseData.success && Array.isArray(responseData.data)) { - const rawTradesFromPool = responseData.data; - const enrichedPoolTrades = await enrichTradeData(rawTradesFromPool, pool.address, effectiveConfig); - const processedPoolTrades = await Promise.all(enrichedPoolTrades.map(trade => processAndFormatTrade(trade, pool.address))); - allTrades.push(...processedPoolTrades); - } else if (responseData && responseData.message) { - console.log(`API message for pool ${pool.address}: ${responseData.message}`); - } else { - console.error(`API returned unexpected data structure for pool ${pool.address}:`, responseData); - } - })); - } - - // Await the fetch promise and log the result - const fetchResult = await fetchPromise; - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] fetchPromise resolved with ${fetchResult?.length || 0} trades`); - - if (useSQLDirectMode) { - allTrades = fetchResult || []; - } - - allTrades.sort((a, b) => dayjs(b.timestamp).valueOf() - dayjs(a.timestamp).valueOf()); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log('Processed trades (fresh fetch):', allTrades); - - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] Final result for fetchTrades - returning ${allTrades.length} trades for address ${address}.`); - - tradeHistoryCache.set(address, { trades: allTrades, timestamp: Date.now() }); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`[TRADE_HISTORY_DEBUG] [FetchID ${currentFetchId}] Cached new trades for address ${address} in module cache.`); - - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 📤 STATE: About to call setTrades with ${allTrades.length} trades.`); - const setTradesStartTime = performance.now(); - setTrades(allTrades); - const setTradesEndTime = performance.now(); - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] ✅ STATE: setTrades called successfully. Time: ${(setTradesEndTime - setTradesStartTime).toFixed(2)}ms`); - - // Calculate total time and release lock - const overallEndTime = performance.now(); - const totalTime = overallEndTime - overallStartTime; - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🏁 COMPLETE: Total pipeline time: ${totalTime.toFixed(2)}ms`); - - return allTrades; - } catch (error) { - console.error(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] ❌ ERROR: Exception during fetchTrades:`, error); - setError(error.message); // Set error state in the hook - // setTrades([]); // Optionally clear trades or handle error state differently - throw error; // Re-throw to allow caller to catch if needed - } finally { - // Always release the processing lock - processingLock.current = false; - activeFetchPromises.delete(address); // Remove promise once operation is complete (success or fail) - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] 🔓 UNLOCK: Processing lock released for ${address}`); - } - })(); - - activeFetchPromises.set(address, fetchPromise); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG: [FetchID ${currentFetchId}] New fetch promise stored for ${address}.`); - return fetchPromise; - - }, [address, effectiveConfig]); // Dependencies: address for cache keys and promise map keys, effectiveConfig for dynamic pools - - React.useEffect(() => { - setLoading(true); - setError(null); - fetchTrades() - .catch(err => { - // Error is already logged and set in fetchTrades, just ensure loading state is handled - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: fetchTrades in useEffect caught an error (expected if already handled).`); - }) - .finally(() => { - setLoading(false); - }); - }, [fetchTrades, currentFetchOperationId]); - - // --- Cascade: Add useEffect for Supabase real-time subscription --- - React.useEffect(() => { - if (!realtime_websocket || !supabase || !address || !isConnected) { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true' && realtime_websocket) { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Skipping Supabase real-time subscription. Conditions not met: realtime_websocket=${realtime_websocket}, supabase=${!!supabase}, address=${address}, isConnected=${isConnected}`); - } - return; - } - - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Setting up Supabase real-time subscription for trades for address: ${address.toLowerCase()}`); - } - - const channelName = `trade-history-${address.toLowerCase()}`; - const tradeChannel = supabase - .channel(channelName) - .on( - 'postgres_changes', - { - event: 'INSERT', - schema: 'public', - table: 'trade_history', - filter: `user_address=eq.${address.toLowerCase()}` - }, - async (payload) => { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Real-time trade update received for ${address.toLowerCase()}:`, payload); - } - const newRawTrade = payload.new; - if (newRawTrade) { - try { - // Ensure the new trade has an ID, which is used as eventId in processAndFormatTrade - if (typeof newRawTrade.id === 'undefined') { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.warn(`DEBUG (ViewModel) [${currentFetchOperationId}]: Real-time trade received without an 'id' field, assigning tx_hash as temporary id:`, newRawTrade.evt_tx_hash); - } - // Attempt to use evt_tx_hash or a unique identifier if id is missing. - // This depends on how your 'id' field is normally populated. - // For now, we'll log a warning. A proper fix might involve ensuring 'id' is always present or adapting processAndFormatTrade. - } - - const enrichedTradesArray = await enrichTradeData([newRawTrade], address, effectiveConfig); - if (enrichedTradesArray && enrichedTradesArray.length > 0) { - const enrichedNewTrade = enrichedTradesArray[0]; - const processedNewTrade = await processAndFormatTrade(enrichedNewTrade, address); - - setTrades((prevTrades) => { - // Check if trade already exists based on txHash and a unique event identifier (like eventId/id) - const tradeExists = prevTrades.some( - (t) => t.txHash === processedNewTrade.txHash && t.eventId === processedNewTrade.eventId - ); - - if (tradeExists) { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Real-time trade ${processedNewTrade.txHash} (ID: ${processedNewTrade.eventId}) already exists in state, skipping update.`); - } - return prevTrades; - } - - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Adding new real-time trade to state:`, processedNewTrade); - } - const updatedTrades = [processedNewTrade, ...prevTrades]; - - // Update cache - tradeHistoryCache.set(address, { trades: updatedTrades, timestamp: Date.now() }); - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Module cache updated with real-time trade for ${address}.`); - } - return updatedTrades; - }); - } else { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.error(`DEBUG (ViewModel) [${currentFetchOperationId}]: Failed to enrich real-time trade data. Raw trade:`, newRawTrade); - } - } - } catch (e) { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.error(`DEBUG (ViewModel) [${currentFetchOperationId}]: Error processing real-time trade. Error:`, e, "Raw trade payload:", newRawTrade); - } - } - } else { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.warn(`DEBUG (ViewModel) [${currentFetchOperationId}]: Real-time trade payload did not contain 'new' trade data. Payload:`, payload); - } - } - } - ) - .subscribe((status, err) => { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Supabase channel '${channelName}' subscription status: ${status}`); - } - if (status === 'SUBSCRIBED') { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Successfully subscribed to 'trade_history' table inserts for user ${address.toLowerCase()} on channel '${channelName}'.`); - } - } else if (status === 'CHANNEL_ERROR') { - console.error(`DEBUG (ViewModel) [${currentFetchOperationId}]: Supabase channel error on '${channelName}' for user ${address.toLowerCase()}. Status: ${status}. Error:`, err); - } else if (status === 'TIMED_OUT') { - console.warn(`DEBUG (ViewModel) [${currentFetchOperationId}]: Supabase subscription timed out for channel '${channelName}' for user ${address.toLowerCase()}. Status: ${status}.`); - } else if (err) { - console.error(`DEBUG (ViewModel) [${currentFetchOperationId}]: Supabase subscription error on channel '${channelName}' for user ${address.toLowerCase()}. Error object:`, err); - } - }); - - return () => { - if (tradeChannel) { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Removing Supabase real-time subscription for trades from channel '${channelName}' for address: ${address.toLowerCase()}`); - } - supabase.removeChannel(tradeChannel) - .then(status => { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Supabase channel '${channelName}' removal status: ${status}`); - } - }) - .catch(error => { - if (process.env.NEXT_PUBLIC_DEBUG_MODE === 'true') { - console.error(`DEBUG (ViewModel) [${currentFetchOperationId}]: Error removing Supabase channel '${channelName}':`, error); - } - }); - } - }; - }, [address, isConnected, supabase, realtime_websocket, currentFetchOperationId, effectiveConfig]); // Added effectiveConfig dependency - // --- End Cascade changes --- - - // --- Cascade: Add useEffect for polling mode (fetch trades every 10 seconds) --- - React.useEffect(() => { - if (!polling_mode || !address || !isConnected) { - if (polling_mode) { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Skipping polling mode. Conditions not met: polling_mode=${polling_mode}, address=${address}, isConnected=${isConnected}`); - } - return; - } - - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Setting up polling mode for trades every ${POLLING_INTERVAL / 1000} seconds for address: ${address.toLowerCase()}`); - - const pollingInterval = setInterval(() => { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Polling interval triggered - fetching fresh trades for ${address.toLowerCase()}`); - fetchTrades(true) // Force refresh to bypass cache - .then(freshTrades => { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Polling fetch completed - got ${freshTrades.length} trades`); - }) - .catch(error => { - console.error(`DEBUG (ViewModel) [${currentFetchOperationId}]: Polling fetch error:`, error); - }); - }, POLLING_INTERVAL); - - return () => { - console.log(`DEBUG (ViewModel) [${currentFetchOperationId}]: Clearing polling interval for address: ${address.toLowerCase()}`); - clearInterval(pollingInterval); - }; - }, [address, isConnected, polling_mode, currentFetchOperationId, fetchTrades]); - // --- End Cascade polling changes --- - - // Debug log hook state before returning - React.useEffect(() => { - console.log(`[TRADE_HISTORY_DEBUG] [${currentFetchOperationId}] Hook state updated - trades: ${trades.length}, loading: ${loading}, error: ${error}`); - }, [trades, loading, error, currentFetchOperationId]); - - return { trades, loading, error, fetchTrades }; +export function useTradeHistory(config = null) { // eslint-disable-line no-unused-vars + // The Supabase trade-history backend (direct table reads, the Trade-History + // edge function, the realtime subscription, and the polling loop) is + // permanently gone. Trade history in the production UI is served by the + // subgraph (SubgraphTradesDataLayer); this hook now returns an empty, + // disabled state so legacy consumers keep working. + const [trades] = React.useState([]); + const fetchTrades = React.useCallback(async () => [], []); + return { trades, loading: false, error: null, fetchTrades }; } // Helper function to open transaction in explorer with chain detection diff --git a/src/components/futarchyFi/marketPage/MarketPageShowcase.jsx b/src/components/futarchyFi/marketPage/MarketPageShowcase.jsx index e1560df..7c59d7a 100644 --- a/src/components/futarchyFi/marketPage/MarketPageShowcase.jsx +++ b/src/components/futarchyFi/marketPage/MarketPageShowcase.jsx @@ -29,7 +29,6 @@ import PositionsTable from "./PositionsTable"; import { useSnapshotData } from '../../../hooks/useSnapshotData'; import MarketBalancePanel from "./MarketBalancePanel"; import PoolDataDisplay from "./PoolDataDisplay"; -import RecentTradesDataLayer from "./RecentTradesDataLayer"; import SubgraphTradesDataLayer from "./SubgraphTradesDataLayer"; import { useYesNoPoolData } from '../../../hooks/usePoolData'; import dayjs from 'dayjs'; // Added dayjs import @@ -5288,40 +5287,22 @@ const MarketPageShowcase = ({ hidden = false, debugMode = false, proposal = null )} {activeTab === 'recent-trades-sdk' && (
- {useSubgraphTrades ? ( - - ) : ( - - )} +
)} {activeTab === 'my-trades-sdk' && (
- {useSubgraphTrades ? ( - - ) : ( - - )} +
)} {activeTab === 'redeem-tokens' && ( diff --git a/src/components/futarchyFi/marketPage/MarketPageShowcaseViewModel.jsx b/src/components/futarchyFi/marketPage/MarketPageShowcaseViewModel.jsx index 58f6ae4..6f12559 100644 --- a/src/components/futarchyFi/marketPage/MarketPageShowcaseViewModel.jsx +++ b/src/components/futarchyFi/marketPage/MarketPageShowcaseViewModel.jsx @@ -1,90 +1,18 @@ import { useState, useEffect } from 'react'; import useLatestPrices from '../../../hooks/useLatestPrices'; -import { createClient } from '@supabase/supabase-js'; -// Initialize Supabase client -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://nvhqdqtlsdboctqjcelq.supabase.co'; -const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; -const supabase = createClient(supabaseUrl, supabaseKey); +// NOTE: The Supabase pool_candles backend is permanently gone. The legacy +// datafeeds below no longer have a data source; the production chart is +// rendered from subgraph data elsewhere. export const createCustomDatafeed = (poolId) => { let cachedData = null; let isFetching = false; const generateHistoricalData = async () => { - try { - // Fetch hourly candles from Supabase instead of external API - const { data, error } = await supabase - .from('pool_candles') - .select('timestamp, price') - .eq('address', poolId) - .eq('interval', '3600000') - .order('timestamp', { ascending: true }) - .limit(1000); - - if (error) { - throw new Error(`Failed to fetch candles from Supabase: ${error.message}`); - } - - const candles = (data || []).map(row => ({ - timestamp: row.timestamp, - average_price: row.price - })); - - // Sort candles by timestamp to ensure chronological order - candles.sort((a, b) => a.timestamp - b.timestamp); - - const processedData = []; - - for (let i = 0; i < candles.length; i++) { - const currentCandle = candles[i]; - const nextCandle = candles[i + 1]; - - // Add the current candle - processedData.push({ - time: currentCandle.timestamp, - open: currentCandle.average_price, - high: currentCandle.average_price, - low: currentCandle.average_price, - close: currentCandle.average_price, - volume: 0 - }); - - // If there's a next candle, fill the gap with hourly bars - if (nextCandle) { - const hourInSeconds = 3600; - const timeDiff = nextCandle.timestamp - currentCandle.timestamp; - const hoursToFill = Math.floor(timeDiff / hourInSeconds) - 1; - - // Fill each hour with the current candle's price until we reach the next candle - for (let hour = 1; hour <= hoursToFill; hour++) { - processedData.push({ - time: currentCandle.timestamp + (hour * hourInSeconds), - open: currentCandle.average_price, - high: currentCandle.average_price, - low: currentCandle.average_price, - close: currentCandle.average_price, - volume: 0 - }); - } - } - } - - console.log('[CustomDatafeed] Generated bars:', { - totalBars: processedData.length, - firstBar: processedData[0], - lastBar: processedData[processedData.length - 1], - timeRange: { - start: new Date(processedData[0].time * 1000).toISOString(), - end: new Date(processedData[processedData.length - 1].time * 1000).toISOString() - } - }); - - return processedData; - } catch (error) { - console.error('[CustomDatafeed] Error generating historical data:', error); - throw error; - } + // The Supabase candle backend that powered this datafeed is gone. + // Return no bars rather than fetching from a dead endpoint. + return []; }; return { @@ -158,40 +86,6 @@ export const createProductionDatafeed = (poolId) => { let cachedData = null; let isFetching = false; - const convertTimestamp = (timestamp) => { - // Convert to milliseconds for TradingView - return timestamp * 1000; - }; - - const filterUniqueTimestamps = (data) => { - console.log('[filterUniqueTimestamps] Input data:', { - count: data.length, - sampleTimestamps: data.slice(0, 3).map(d => ({ - time: d.time, - date: new Date(d.time).toISOString() - })) - }); - - const seen = new Set(); - const filtered = data.filter(item => { - if (seen.has(item.time)) { - return false; - } - seen.add(item.time); - return true; - }); - - console.log('[filterUniqueTimestamps] Output data:', { - count: filtered.length, - sampleTimestamps: filtered.slice(0, 3).map(d => ({ - time: d.time, - date: new Date(d.time).toISOString() - })) - }); - - return filtered; - }; - return { onReady: (callback) => { console.log('[ProductionDatafeed] onReady called'); @@ -260,68 +154,13 @@ export const createProductionDatafeed = (poolId) => { return; } + // The Supabase candle backend that powered this datafeed is gone. + // Cache an empty dataset and report no data rather than fetching + // from a dead endpoint. isFetching = true; - console.log('[ProductionDatafeed] Fetching new data from Supabase'); - const { data, error } = await supabase - .from('pool_candles') - .select('timestamp, price') - .eq('address', poolId) - .eq('interval', '3600000') - .order('timestamp', { ascending: true }) - .limit(2000); - - if (error) { - throw new Error(`Failed to fetch candles from Supabase: ${error.message}`); - } - - const rows = data || []; - console.log('[ProductionDatafeed] Supabase response:', { - totalCandles: rows.length, - sampleCandles: rows.slice(0, 3).map(c => ({ - timestamp: c.timestamp, - date: new Date(c.timestamp * 1000).toISOString(), - price: c.price - })) - }); - - // Transform and cache the data with unique timestamps - const transformedData = rows.map(candle => ({ - time: convertTimestamp(candle.timestamp), // Convert to milliseconds - open: candle.price, - high: candle.price, - low: candle.price, - close: candle.price, - volume: 0 - })); - - // Filter out duplicate timestamps and sort by time - cachedData = filterUniqueTimestamps(transformedData).sort((a, b) => a.time - b.time); - - // Filter cached data for the requested range - const bars = cachedData.filter(bar => - bar.time >= from * 1000 && bar.time <= to * 1000 - ); - - console.log('[ProductionDatafeed] Final filtered bars:', { - requestedRange: { - from: new Date(from * 1000).toISOString(), - to: new Date(to * 1000).toISOString() - }, - barsReturned: bars.length, - firstBar: bars[0] ? { - time: bars[0].time, - date: new Date(bars[0].time).toISOString(), - price: bars[0].close - } : null, - lastBar: bars[bars.length - 1] ? { - time: bars[bars.length - 1].time, - date: new Date(bars[bars.length - 1].time).toISOString(), - price: bars[bars.length - 1].close - } : null - }); - + cachedData = []; isFetching = false; - onHistoryCallback(bars, { noData: bars.length === 0 }); + onHistoryCallback([], { noData: true }); } catch (error) { isFetching = false; console.error('[ProductionDatafeed] Error fetching bars:', error); diff --git a/src/components/futarchyFi/marketPage/RecentTradesDataLayer.jsx b/src/components/futarchyFi/marketPage/RecentTradesDataLayer.jsx deleted file mode 100644 index 6a420a5..0000000 --- a/src/components/futarchyFi/marketPage/RecentTradesDataLayer.jsx +++ /dev/null @@ -1,1002 +0,0 @@ -import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; -import { useAccount } from 'wagmi'; -import { createClient } from '@supabase/supabase-js'; -import { DataLayer } from '../../../../futarchy-sdk/DataLayer.js'; -import { TradeHistoryFetcher } from '../../../../futarchy-sdk/fetchers/TradeHistoryFetcher.js'; -import { ERC20Fetcher } from '../../../../futarchy-sdk/fetchers/ERC20Fetcher.js'; -import TradeHistoryCartridge from '../../../../futarchy-sdk/cartridges/TradeHistoryCartridge.js'; -import { formatWith } from '../../../utils/precisionFormatter'; - -const RecentTradesDataLayer = ({ - config, - tokenImages = { company: null, currency: null }, - showMyTrades = false, - limit = 30, - cachedData = null, - onDataFetched = null, - dataKey = null -}) => { - const [allTrades, setAllTrades] = useState([]); // Store all trades - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [newTradeId, setNewTradeId] = useState(null); // Track newly arrived trade for animation - const [lastFetchedDataKey, setLastFetchedDataKey] = useState(null); - const { address, isConnected } = useAccount(); - const dataLayerRef = useRef(null); - const supabaseClientRef = useRef(null); - const subscriptionStartTime = useRef(Date.now()); // Track when subscription started to avoid processing old trades - - // Initialize DataLayer with SDK components - useEffect(() => { - const initializeDataLayer = async () => { - try { - console.log('[RecentTradesDataLayer] Initializing DataLayer...'); - - // Get chainId from config (default to 100 for Gnosis if not set) - const chainId = config?.chainId || 100; - const rpcUrl = chainId === 1 - ? process.env.NEXT_PUBLIC_ETHEREUM_RPC || 'https://eth.llamarpc.com' - : process.env.NEXT_PUBLIC_GNOSIS_RPC || 'https://rpc.gnosischain.com'; - - console.log(`[RecentTradesDataLayer] Using chain ${chainId} with RPC: ${rpcUrl}`); - - // Create DataLayer instance - const dataLayer = new DataLayer(); - - // Initialize ERC20Fetcher for token metadata - const erc20Fetcher = new ERC20Fetcher({ - chainId: chainId, - rpcUrl: rpcUrl - }); - - // Create Supabase client (required by TradeHistoryFetcher) - const supabaseClient = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY - ); - - // Store supabase client for realtime subscriptions - supabaseClientRef.current = supabaseClient; - - console.log('[RecentTradesDataLayer] Supabase client created'); - - // Initialize TradeHistoryFetcher with Supabase client as first param - const tradeHistoryFetcher = new TradeHistoryFetcher(supabaseClient, { - erc20Fetcher: erc20Fetcher, - rpcUrl: rpcUrl, - chainId: chainId - }); - - // PRE-POPULATE token cache from metadata - NO RPC CALLS NEEDED! - console.log('[RecentTradesDataLayer] 🔍 Checking config for metadata...'); - - // metadata is at config.metadata (NOT config.marketInfo.metadata) - const metadata = config?.metadata; - - if (metadata?.companyTokens || metadata?.currencyTokens) { - console.log('[RecentTradesDataLayer] 🎯 Found metadata with token info! Pre-populating cache...'); - - // Cache company tokens from metadata - if (metadata.companyTokens) { - const { yes, no, base } = metadata.companyTokens; - - if (yes?.wrappedCollateralTokenAddress && yes?.tokenSymbol) { - tradeHistoryFetcher.tokenCache.set(yes.wrappedCollateralTokenAddress.toLowerCase(), { - data: { symbol: yes.tokenSymbol, name: yes.tokenName || yes.tokenSymbol, decimals: 18 }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${yes.tokenSymbol} at ${yes.wrappedCollateralTokenAddress}`); - } - - if (no?.wrappedCollateralTokenAddress && no?.tokenSymbol) { - tradeHistoryFetcher.tokenCache.set(no.wrappedCollateralTokenAddress.toLowerCase(), { - data: { symbol: no.tokenSymbol, name: no.tokenName || no.tokenSymbol, decimals: 18 }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${no.tokenSymbol} at ${no.wrappedCollateralTokenAddress}`); - } - - if (base?.wrappedCollateralTokenAddress && base?.tokenSymbol) { - tradeHistoryFetcher.tokenCache.set(base.wrappedCollateralTokenAddress.toLowerCase(), { - data: { symbol: base.tokenSymbol, name: base.tokenName || base.tokenSymbol, decimals: 18 }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${base.tokenSymbol} at ${base.wrappedCollateralTokenAddress}`); - } - } - - // Cache currency tokens from metadata - if (metadata.currencyTokens) { - const { yes, no, base } = metadata.currencyTokens; - - if (yes?.wrappedCollateralTokenAddress && yes?.tokenSymbol) { - tradeHistoryFetcher.tokenCache.set(yes.wrappedCollateralTokenAddress.toLowerCase(), { - data: { symbol: yes.tokenSymbol, name: yes.tokenName || yes.tokenSymbol, decimals: 18 }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${yes.tokenSymbol} at ${yes.wrappedCollateralTokenAddress}`); - } - - if (no?.wrappedCollateralTokenAddress && no?.tokenSymbol) { - tradeHistoryFetcher.tokenCache.set(no.wrappedCollateralTokenAddress.toLowerCase(), { - data: { symbol: no.tokenSymbol, name: no.tokenName || no.tokenSymbol, decimals: 18 }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${no.tokenSymbol} at ${no.wrappedCollateralTokenAddress}`); - } - - if (base?.wrappedCollateralTokenAddress && base?.tokenSymbol) { - tradeHistoryFetcher.tokenCache.set(base.wrappedCollateralTokenAddress.toLowerCase(), { - data: { symbol: base.tokenSymbol, name: base.tokenName || base.tokenSymbol, decimals: 18 }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${base.tokenSymbol} at ${base.wrappedCollateralTokenAddress}`); - } - } - - console.log('[RecentTradesDataLayer] ✅ Token cache populated from metadata - zero RPC calls!'); - console.log('[RecentTradesDataLayer] Cached tokens:', Array.from(tradeHistoryFetcher.tokenCache.keys())); - } else if (config?.BASE_TOKENS_CONFIG) { - console.log('[RecentTradesDataLayer] Pre-populating token cache from config...'); - console.log('[RecentTradesDataLayer] Full config:', JSON.stringify(config.BASE_TOKENS_CONFIG, null, 2)); - - // Cache company tokens (GNO/YES-GNO/NO-GNO) - if (config.BASE_TOKENS_CONFIG.company) { - const companyTokens = config.BASE_TOKENS_CONFIG.company; - console.log('[RecentTradesDataLayer] Company tokens:', companyTokens); - - // Base company token - if (companyTokens.address) { - tradeHistoryFetcher.tokenCache.set(companyTokens.address.toLowerCase(), { - data: { - symbol: companyTokens.symbol || 'COMPANY', - name: companyTokens.name || companyTokens.symbol || 'COMPANY', - decimals: 18 - }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${companyTokens.symbol} at ${companyTokens.address}`); - } - - // YES token - check multiple possible field names - const yesTokenAddr = companyTokens.yesToken || companyTokens.yesAddress || companyTokens.yes_token || companyTokens.yes_address; - if (yesTokenAddr) { - tradeHistoryFetcher.tokenCache.set(yesTokenAddr.toLowerCase(), { - data: { - symbol: `YES_${companyTokens.symbol || 'COMPANY'}`, - name: `YES ${companyTokens.name || companyTokens.symbol || 'COMPANY'}`, - decimals: 18 - }, - timestamp: Date.now() - }); - console.log(` ✓ Cached YES_${companyTokens.symbol} at ${yesTokenAddr}`); - } - - // NO token - check multiple possible field names - const noTokenAddr = companyTokens.noToken || companyTokens.noAddress || companyTokens.no_token || companyTokens.no_address; - if (noTokenAddr) { - tradeHistoryFetcher.tokenCache.set(noTokenAddr.toLowerCase(), { - data: { - symbol: `NO_${companyTokens.symbol || 'COMPANY'}`, - name: `NO ${companyTokens.name || companyTokens.symbol || 'COMPANY'}`, - decimals: 18 - }, - timestamp: Date.now() - }); - console.log(` ✓ Cached NO_${companyTokens.symbol} at ${noTokenAddr}`); - } - } - - // Cache currency tokens (sDAI/YES-sDAI/NO-sDAI) - if (config.BASE_TOKENS_CONFIG.currency) { - const currencyTokens = config.BASE_TOKENS_CONFIG.currency; - console.log('[RecentTradesDataLayer] Currency tokens:', currencyTokens); - - // Base currency token - if (currencyTokens.address) { - tradeHistoryFetcher.tokenCache.set(currencyTokens.address.toLowerCase(), { - data: { - symbol: currencyTokens.symbol || 'sDAI', - name: currencyTokens.name || currencyTokens.symbol || 'sDAI', - decimals: 18 - }, - timestamp: Date.now() - }); - console.log(` ✓ Cached ${currencyTokens.symbol} at ${currencyTokens.address}`); - } - - // YES token - check multiple possible field names - const yesTokenAddr = currencyTokens.yesToken || currencyTokens.yesAddress || currencyTokens.yes_token || currencyTokens.yes_address; - if (yesTokenAddr) { - tradeHistoryFetcher.tokenCache.set(yesTokenAddr.toLowerCase(), { - data: { - symbol: `YES_${currencyTokens.symbol || 'sDAI'}`, - name: `YES ${currencyTokens.name || currencyTokens.symbol || 'sDAI'}`, - decimals: 18 - }, - timestamp: Date.now() - }); - console.log(` ✓ Cached YES_${currencyTokens.symbol} at ${yesTokenAddr}`); - } - - // NO token - check multiple possible field names - const noTokenAddr = currencyTokens.noToken || currencyTokens.noAddress || currencyTokens.no_token || currencyTokens.no_address; - if (noTokenAddr) { - tradeHistoryFetcher.tokenCache.set(noTokenAddr.toLowerCase(), { - data: { - symbol: `NO_${currencyTokens.symbol || 'sDAI'}`, - name: `NO ${currencyTokens.name || currencyTokens.symbol || 'sDAI'}`, - decimals: 18 - }, - timestamp: Date.now() - }); - console.log(` ✓ Cached NO_${currencyTokens.symbol} at ${noTokenAddr}`); - } - } - - console.log('[RecentTradesDataLayer] Token cache populated successfully'); - console.log('[RecentTradesDataLayer] Cached tokens:', Array.from(tradeHistoryFetcher.tokenCache.keys())); - } else { - console.warn('[RecentTradesDataLayer] No BASE_TOKENS_CONFIG found in config!'); - console.log('[RecentTradesDataLayer] Full config:', config); - } - - // Register fetcher - dataLayer.registerFetcher(tradeHistoryFetcher); - console.log('[RecentTradesDataLayer] TradeHistoryFetcher registered'); - - // Initialize and register cartridge - const tradeHistoryCartridge = new TradeHistoryCartridge(dataLayer); - - // Set metadata on cartridge for token type identification - if (metadata) { - console.log('[RecentTradesDataLayer] Setting metadata on cartridge...'); - tradeHistoryCartridge.setMetadata(metadata); - } else { - console.warn('[RecentTradesDataLayer] No metadata available for cartridge'); - } - - dataLayer.registerExecutor(tradeHistoryCartridge); - console.log('[RecentTradesDataLayer] TradeHistoryCartridge registered'); - - dataLayerRef.current = dataLayer; - console.log('[RecentTradesDataLayer] DataLayer initialized successfully'); - } catch (err) { - console.error('[RecentTradesDataLayer] Failed to initialize DataLayer:', err); - setError(`Initialization failed: ${err.message}`); - setIsLoading(false); - } - }; - - initializeDataLayer(); - }, [config]); - - // Fetch trades when config, filter, or limit changes - useEffect(() => { - const fetchTrades = async () => { - if (!dataLayerRef.current) { - console.log('[RecentTradesDataLayer] DataLayer not initialized yet'); - return; - } - - // Get proposal ID from config (NOT lowercased) - const proposalId = config?.proposalId || config?.MARKET_ADDRESS; - - if (!proposalId) { - console.log('[RecentTradesDataLayer] Waiting for config to load...'); - // Keep loading state if config hasn't loaded yet - setIsLoading(true); - return; - } - - // Check if we already have cached data for this dataKey - if (cachedData && dataKey && dataKey === lastFetchedDataKey) { - console.log('[RecentTradesDataLayer] ✅ Using cached data - SKIPPING FETCH'); - setAllTrades(cachedData); - setIsLoading(false); - return; - } - - console.log(`[RecentTradesDataLayer] Fetching ALL trades for proposal:`, { - proposalId: proposalId, - limit: limit, - source: 'DataLayer SDK' - }); - - setIsLoading(true); - setError(null); - - try { - // Build fetch params - add userAddress filter when showing "My Trades" - const fetchParams = { - proposalId: proposalId, // Use original case from config - DON'T lowercase - limit: limit, - // CRITICAL FIX: Filter by user address when showMyTrades is true - ...(showMyTrades && address ? { userAddress: address.toLowerCase() } : {}) - }; - - // Use the DataLayer to execute trades.formatted operation - for await (const step of dataLayerRef.current.execute('trades.formatted', fetchParams)) { - if (step.status === 'processing') { - console.log('[RecentTradesDataLayer]', step.message); - } else if (step.status === 'success') { - const trades = step.data.trades || []; - console.log(`[RecentTradesDataLayer] Successfully fetched ${trades.length} trades`); - setAllTrades(trades); - setLastFetchedDataKey(dataKey); - - // Cache the data in parent - if (onDataFetched) { - onDataFetched(trades); - } - setIsLoading(false); - } else if (step.status === 'error') { - console.error('[RecentTradesDataLayer] Error:', step.message); - setError(step.message); - setIsLoading(false); - } - } - } catch (err) { - console.error('[RecentTradesDataLayer] Fetch error:', err); - setError(`Failed to fetch trades: ${err.message}`); - setIsLoading(false); - } - }; - - fetchTrades(); - }, [config?.proposalId, config?.MARKET_ADDRESS, limit, showMyTrades, address]); // Refetch when showMyTrades or address changes - - // Set up realtime subscription for new trades - useEffect(() => { - if (!supabaseClientRef.current) { - console.log('[RecentTradesDataLayer] Waiting for Supabase client...'); - return; - } - - if (!config?.proposalId && !config?.MARKET_ADDRESS) { - console.log('[RecentTradesDataLayer] Waiting for config...'); - return; - } - - if (!dataLayerRef.current) { - console.log('[RecentTradesDataLayer] Waiting for DataLayer...'); - return; - } - - const proposalId = config?.proposalId || config?.MARKET_ADDRESS; - - console.log('[RecentTradesDataLayer] 🔴 Setting up realtime subscription for proposal:', proposalId); - - const channel = supabaseClientRef.current - .channel('realtime-trade-history') - .on('postgres_changes', { - event: 'INSERT', // Only listen for new inserts, not updates or deletes - schema: 'public', - table: 'trade_history' - }, (payload) => { - console.log('[RecentTradesDataLayer] 🔴 RAW PAYLOAD RECEIVED (INSERT):', JSON.stringify(payload, null, 2)); - - // Filter manually for this proposal - check proposal_id in lowercase only - const payloadProposalId = (payload.new?.proposal_id || '').toLowerCase(); - const expectedProposalId = (proposalId || '').toLowerCase(); - - console.log('[RecentTradesDataLayer] 🔍 Filter check:', { - payloadProposalId, - expectedProposalId, - match: payloadProposalId === expectedProposalId - }); - - if (payloadProposalId !== expectedProposalId) { - console.log('[RecentTradesDataLayer] ⏭️ SKIPPING - proposal_id mismatch'); - return; - } - - console.log('[RecentTradesDataLayer] ✅ PASSED FILTER - Processing trade'); - - // Format the new trade using the cartridge - try { - const cartridge = dataLayerRef.current.executors.get('trades.formatted'); - if (!cartridge) { - console.error('[RecentTradesDataLayer] ❌ Cartridge not found!'); - return; - } - - if (!cartridge.formatTrade) { - console.error('[RecentTradesDataLayer] ❌ formatTrade method not found!'); - return; - } - - console.log('[RecentTradesDataLayer] 🔄 Formatting new trade...'); - console.log('[RecentTradesDataLayer] 🔄 Raw trade data:', payload.new); - const formattedTrade = cartridge.formatTrade(payload.new); - console.log('[RecentTradesDataLayer] ✅ Formatted trade:', JSON.stringify(formattedTrade, null, 2)); - - // Generate unique ID for duplicate detection - const uniqueTradeId = payload.new.id || payload.new.evt_tx_hash || - `${payload.new.evt_block_number}-${payload.new.evt_index || payload.new.log_index}`; - - // Add to the beginning of the trades array - with duplicate check - setAllTrades(prevTrades => { - console.log('[RecentTradesDataLayer] 📝 Adding trade to list. Previous count:', prevTrades.length); - - // Check if this trade already exists by transaction hash - const newTxHash = payload.new.evt_tx_hash; - const newBlockNumber = payload.new.evt_block_number; - const newLogIndex = payload.new.evt_index || payload.new.log_index; - - const isDuplicate = prevTrades.some(trade => { - // Extract tx hash from transactionLink (e.g., "https://gnosisscan.io/tx/0xabc...") - const existingTxHash = trade.transactionLink ? - trade.transactionLink.split('/tx/')[1]?.split('#')[0] : null; - - // Match by transaction hash (primary check) - if (newTxHash && existingTxHash === newTxHash) { - return true; - } - - // Match by block number + log index (secondary check) - if (newBlockNumber && newLogIndex && - trade.blockNumber === newBlockNumber && - trade.logIndex === newLogIndex) { - return true; - } - - return false; - }); - - if (isDuplicate) { - console.log('[RecentTradesDataLayer] ⚠️ Duplicate trade detected, skipping tx:', newTxHash); - return prevTrades; - } - - const updatedTrades = [formattedTrade, ...prevTrades]; - - // Also update the cached data in parent - if (onDataFetched) { - onDataFetched(updatedTrades); - } - - return updatedTrades; - }); - - // Set the new trade ID for animation - const tradeId = payload.new.id || payload.new.evt_tx_hash || payload.new.evt_block_number; - console.log('[RecentTradesDataLayer] 🎨 Setting animation for trade ID:', tradeId); - setNewTradeId(tradeId); - - // Clear the animation after 3 seconds - setTimeout(() => { - console.log('[RecentTradesDataLayer] 🎨 Clearing animation'); - setNewTradeId(null); - }, 3000); - - console.log('[RecentTradesDataLayer] ✅ New trade added and animated!'); - } catch (err) { - console.error('[RecentTradesDataLayer] ❌ Error formatting realtime trade:', err); - console.error('[RecentTradesDataLayer] Error stack:', err.stack); - } - }) - .subscribe((status) => { - console.log('[RecentTradesDataLayer] 🔴 Subscription status:', status); - if (status === 'SUBSCRIBED') { - console.log('[RecentTradesDataLayer] ✅ Successfully subscribed to trade_history realtime updates'); - } else if (status === 'CHANNEL_ERROR') { - console.error('[RecentTradesDataLayer] ❌ Channel error - subscription failed'); - } else if (status === 'TIMED_OUT') { - console.error('[RecentTradesDataLayer] ❌ Subscription timed out'); - } - }); - - // Log channel state - console.log('[RecentTradesDataLayer] 🔴 Channel created:', { - channelName: 'realtime-trade-history', - table: 'trade_history', - proposalFilter: proposalId - }); - - // Cleanup on unmount - return () => { - console.log('[RecentTradesDataLayer] 🔴 Cleaning up realtime subscription'); - if (supabaseClientRef.current) { - supabaseClientRef.current.removeChannel(channel); - } - }; - }, [config?.proposalId, config?.MARKET_ADDRESS]); - - const companySymbols = useMemo(() => { - const symbols = new Set(); - const addSymbol = (symbol) => { - if (symbol && typeof symbol === 'string') { - symbols.add(symbol.toUpperCase()); - } - }; - - addSymbol(config?.BASE_TOKENS_CONFIG?.company?.symbol); - addSymbol(config?.MERGE_CONFIG?.companyPositions?.yes?.wrap?.tokenSymbol); - addSymbol(config?.MERGE_CONFIG?.companyPositions?.no?.wrap?.tokenSymbol); - - return symbols; - }, [config]); - - const isCompanyToken = useCallback((symbol) => { - if (!symbol) return false; - const upper = symbol.toUpperCase(); - if (companySymbols.has(upper)) return true; - for (const baseSymbol of companySymbols) { - if (!baseSymbol) continue; - if (upper.endsWith(`_${baseSymbol}`)) { - return true; - } - } - return false; - }, [companySymbols]); - - const roundUp = useCallback((value, decimals) => { - const factor = Math.pow(10, decimals); - if (value >= 0) { - return Math.ceil((value + Number.EPSILON) * factor) / factor; - } - return -Math.ceil((Math.abs(value) + Number.EPSILON) * factor) / factor; - }, []); - - const formatTokenAmount = useCallback((rawValue, symbol) => { - const parsed = typeof rawValue === 'number' ? rawValue : parseFloat(rawValue); - const isCompany = isCompanyToken(symbol); - if (!isFinite(parsed)) { - return rawValue ?? (isCompany ? '0.0000' : '0.00'); - } - - const decimals = isCompany ? 4 : 2; - const factor = Math.pow(10, decimals); - let rounded; - - if (isCompany) { - rounded = roundUp(parsed, decimals); - } else { - rounded = Math.round(parsed * factor) / factor; - } - - if (Object.is(rounded, -0)) { - rounded = 0; - } - - return rounded.toFixed(decimals); - }, [isCompanyToken, roundUp]); - - const formatPrice = useCallback((rawPrice) => { - const parsed = typeof rawPrice === 'number' ? rawPrice : parseFloat(rawPrice); - if (!isFinite(parsed)) { - return rawPrice ?? '0.00'; - } - const rounded = Math.round(parsed * 100) / 100; - return rounded.toFixed(2); - }, []); - - // Filter trades locally based on showMyTrades - NO REFETCHING! - const filteredTrades = useMemo(() => { - if (!showMyTrades) { - return allTrades; // Show all trades - } - - // For "My Trades" mode: if wallet not connected, show empty array - if (!address) { - return []; - } - - // Filter to only user's trades by checking if user address matches buyer or seller - return allTrades.filter(trade => { - // Check if trade has user info and matches current user - // Trade object might have userAddress, buyer, or seller fields - const tradeUser = (trade.userAddress || trade.buyer || trade.seller || '').toLowerCase(); - const currentUser = address.toLowerCase(); - return tradeUser === currentUser; - }); - }, [allTrades, showMyTrades, address]); - - console.log(`[RecentTradesDataLayer] Displaying ${filteredTrades.length} of ${allTrades.length} trades (${showMyTrades ? 'My Trades' : 'All Trades'})`); - - // Loading state - if (isLoading) { - return ( -
-
-
-

Loading trade history...

-
-
- ); - } - - // Error state - if (error) { - return ( -
-
-

Error loading trades

-

{error}

-
-
- ); - } - - // Format date helper - const formatDate = (timestamp) => { - const date = new Date(timestamp); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${month}/${day} ${hours}:${minutes}`; - }; - - // Handle load more button - const handleLoadMore = () => { - setLimit(prevLimit => prevLimit + 30); // Load 30 more trades - }; - - // No trades after filtering - if (filteredTrades.length === 0 && !isLoading) { - return ( -
- {showMyTrades && !isConnected - ? 'Connect wallet to view your trades' - : showMyTrades - ? 'You have no trades for this proposal' - : 'No trades found for this proposal'} -
- ); - } - - // Render trades table - return ( -
- {/* Desktop Table View - hidden on mobile */} -
-
-
- - - - - - - - - -
OutcomeAmountPriceDate
-
-
-
- - - {filteredTrades.map((trade, index) => { - const tokenInSymbol = trade?.amount?.tokenIN?.symbol || ''; - const tokenOutSymbol = trade?.amount?.tokenOUT?.symbol || ''; - - const isYes = trade.outcome.eventSide === 'yes'; - const isNo = trade.outcome.eventSide === 'no'; - const isBuy = trade.outcome.operationSide === 'buy'; - - // Check if this trade involves BASE TOKEN (prediction market) - const tokenInAddress = trade?.amount?.tokenIN?.address?.toLowerCase() || ''; - const tokenOutAddress = trade?.amount?.tokenOUT?.address?.toLowerCase() || ''; - - const baseTokenAddress = config?.metadata?.currencyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase() || - config?.metadata?.companyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase() || ''; - - const isPredictionMarket = baseTokenAddress && ( - tokenInAddress === baseTokenAddress || - tokenOutAddress === baseTokenAddress - ); - - // Detect if tokens are company or currency based on metadata - const isCompanyToken = (address) => { - const addr = address?.toLowerCase(); - return addr === config?.metadata?.companyTokens?.yes?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.companyTokens?.no?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.companyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase(); - }; - - const isCurrencyToken = (address) => { - const addr = address?.toLowerCase(); - return addr === config?.metadata?.currencyTokens?.yes?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.currencyTokens?.no?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.currencyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase(); - }; - - // Get precision config - handle both config.PRECISION_CONFIG and config.display structures - const precisionConfig = config?.PRECISION_CONFIG || (config?.display ? { display: config.display } : null); - - // Format token amounts with proper precision based on token type - let formattedTokenInValue, formattedTokenOutValue; - if (isCompanyToken(tokenInAddress)) { - formattedTokenInValue = formatWith(trade?.amount?.tokenIN?.value, 'company', precisionConfig); - } else if (isCurrencyToken(tokenInAddress)) { - formattedTokenInValue = formatWith(trade?.amount?.tokenIN?.value, 'currency', precisionConfig); - } else { - formattedTokenInValue = formatWith(trade?.amount?.tokenIN?.value, 'amount', precisionConfig); - } - - if (isCompanyToken(tokenOutAddress)) { - formattedTokenOutValue = formatWith(trade?.amount?.tokenOUT?.value, 'company', precisionConfig); - } else if (isCurrencyToken(tokenOutAddress)) { - formattedTokenOutValue = formatWith(trade?.amount?.tokenOUT?.value, 'currency', precisionConfig); - } else { - formattedTokenOutValue = formatWith(trade?.amount?.tokenOUT?.value, 'amount', precisionConfig); - } - - // Format price - if prediction market, show as percentage - // Otherwise, price is ALWAYS in currency (not company tokens) - let formattedPrice; - if (isPredictionMarket) { - const priceValue = parseFloat(trade?.price) || 0; - const percentageValue = priceValue * 100; - formattedPrice = `${formatWith(percentageValue, 'percentage', precisionConfig)}%`; - } else { - // Non-prediction market: price is in currency, so use currency precision - formattedPrice = formatWith(trade?.price, 'currency', precisionConfig); - } - - // Check if this is the newly arrived trade - const tradeId = `${trade.transactionLink}-${trade.blockNumber}`; - const isNewTrade = newTradeId && ( - tradeId.includes(newTradeId) || - trade.transactionLink?.includes(newTradeId) || - String(trade.blockNumber) === String(newTradeId) - ); - - return ( - - - - - - - ); - })} - -
-
-
-
- {trade.outcome.eventSide.toUpperCase()} -
-
- {trade.outcome.operationSide === 'buy' ? 'sell' : 'buy'} -
-
-
-
-
- - {formattedTokenInValue} {tokenInSymbol} - in - - - {formattedTokenOutValue} {tokenOutSymbol} - out - -
-
- - {formattedPrice} - - -
- {formatDate(trade.date)} - {trade.transactionLink && ( - - - - - - - - )} -
-
-
-
-
-
- - {/* Mobile Card View - shown on mobile only */} -
-
-
- {filteredTrades.map((trade, index) => { - const tokenInSymbol = trade?.amount?.tokenIN?.symbol || ''; - const tokenOutSymbol = trade?.amount?.tokenOUT?.symbol || ''; - - const isYes = trade.outcome.eventSide === 'yes'; - const isNo = trade.outcome.eventSide === 'no'; - const isBuy = trade.outcome.operationSide === 'buy'; - - // Check if this trade involves BASE TOKEN (prediction market) - const tokenInAddress = trade?.amount?.tokenIN?.address?.toLowerCase() || ''; - const tokenOutAddress = trade?.amount?.tokenOUT?.address?.toLowerCase() || ''; - - const baseTokenAddress = config?.metadata?.currencyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase() || - config?.metadata?.companyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase() || ''; - - const isPredictionMarket = baseTokenAddress && ( - tokenInAddress === baseTokenAddress || - tokenOutAddress === baseTokenAddress - ); - - // Detect if tokens are company or currency based on metadata - const isCompanyToken = (address) => { - const addr = address?.toLowerCase(); - return addr === config?.metadata?.companyTokens?.yes?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.companyTokens?.no?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.companyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase(); - }; - - const isCurrencyToken = (address) => { - const addr = address?.toLowerCase(); - return addr === config?.metadata?.currencyTokens?.yes?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.currencyTokens?.no?.wrappedCollateralTokenAddress?.toLowerCase() || - addr === config?.metadata?.currencyTokens?.base?.wrappedCollateralTokenAddress?.toLowerCase(); - }; - - // Get precision config - handle both config.PRECISION_CONFIG and config.display structures - const precisionConfig = config?.PRECISION_CONFIG || (config?.display ? { display: config.display } : null); - - // Format token amounts with proper precision based on token type - let formattedTokenInValue, formattedTokenOutValue; - if (isCompanyToken(tokenInAddress)) { - formattedTokenInValue = formatWith(trade?.amount?.tokenIN?.value, 'company', precisionConfig); - } else if (isCurrencyToken(tokenInAddress)) { - formattedTokenInValue = formatWith(trade?.amount?.tokenIN?.value, 'currency', precisionConfig); - } else { - formattedTokenInValue = formatWith(trade?.amount?.tokenIN?.value, 'amount', precisionConfig); - } - - if (isCompanyToken(tokenOutAddress)) { - formattedTokenOutValue = formatWith(trade?.amount?.tokenOUT?.value, 'company', precisionConfig); - } else if (isCurrencyToken(tokenOutAddress)) { - formattedTokenOutValue = formatWith(trade?.amount?.tokenOUT?.value, 'currency', precisionConfig); - } else { - formattedTokenOutValue = formatWith(trade?.amount?.tokenOUT?.value, 'amount', precisionConfig); - } - - // Format price - if prediction market, show as percentage - // Otherwise, price is ALWAYS in currency (not company tokens) - let formattedPrice; - if (isPredictionMarket) { - const priceValue = parseFloat(trade?.price) || 0; - const percentageValue = priceValue * 100; - formattedPrice = `${formatWith(percentageValue, 'percentage', precisionConfig)}%`; - } else { - // Non-prediction market: price is in currency, so use currency precision - formattedPrice = formatWith(trade?.price, 'currency', precisionConfig); - } - - // Check if this is the newly arrived trade - const tradeId = `${trade.transactionLink}-${trade.blockNumber}`; - const isNewTrade = newTradeId && ( - tradeId.includes(newTradeId) || - trade.transactionLink?.includes(newTradeId) || - String(trade.blockNumber) === String(newTradeId) - ); - - return ( -
- {/* Trader Address */} - - - {/* Trade Details Grid */} -
- {/* Outcome */} -
-
Outcome
-
-
- {trade.outcome.eventSide.toUpperCase()} -
-
- {trade.outcome.operationSide === 'buy' ? 'sell' : 'buy'} -
-
-
- - {/* Amount In */} -
-
Amount In
-
- {formattedTokenInValue} {tokenInSymbol} -
-
- - {/* Amount Out */} -
-
Amount Out
-
- {formattedTokenOutValue} {tokenOutSymbol} -
-
- - {/* Price */} -
-
Price
-
- {formattedPrice} -
-
- - {/* Date */} -
-
Date
-
- {formatDate(trade.date)} -
-
-
-
- ); - })} -
-
-
-
- ); -}; - -export default RecentTradesDataLayer; From 37805b5a8d6a9767e2e1a02aaa58259678e19494 Mon Sep 17 00:00:00 2001 From: krandder Date: Sun, 5 Jul 2026 23:38:19 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Update=20auto-qa=20baselines:=20supabase=20?= =?UTF-8?q?imports=2010=E2=86=920,=20GNOSIS=5FRPCS=205=E2=86=924?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The supabase ratchet fired as designed (this PR removed the last 10 imports). The GNOSIS_RPCS count went stale on main when #89 dropped the dead Blast entry after the auto-qa suite (#75) pinned it at 5. --- auto-qa/tests/algebra-pool-price.test.mjs | 10 +++++----- auto-qa/tests/dead-references.test.mjs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/auto-qa/tests/algebra-pool-price.test.mjs b/auto-qa/tests/algebra-pool-price.test.mjs index 0f2f715..17b436c 100644 --- a/auto-qa/tests/algebra-pool-price.test.mjs +++ b/auto-qa/tests/algebra-pool-price.test.mjs @@ -16,7 +16,7 @@ * This file targets the newer Algebra Integral shape. A regression * that flips them silently mis-decodes globalState in production. * - * 2. GNOSIS_RPCS — 5 endpoints, HTTPS-only, deduplicated. Cross-pin + * 2. GNOSIS_RPCS — 4 endpoints, HTTPS-only, deduplicated. Cross-pin * vs the canonical lists in getRpcUrl.js / providers.jsx (already * covered in rpc-config.test.mjs but here as a third occurrence * worth pinning so the duplication is visible). @@ -121,15 +121,15 @@ test('cross-file divergence — algebraQuoter.js POOL_ABI uses the OLDER V3 7-fi }); // --------------------------------------------------------------------------- -// GNOSIS_RPCS — 5 endpoints, HTTPS-only, deduplicated +// GNOSIS_RPCS — 4 endpoints, HTTPS-only, deduplicated // --------------------------------------------------------------------------- -test('GNOSIS_RPCS — has exactly 5 entries (drift surfaces as more/fewer fallback options)', () => { +test('GNOSIS_RPCS — has exactly 4 entries (drift surfaces as more/fewer fallback options)', () => { const m = SRC.match(/GNOSIS_RPCS\s*=\s*\[([\s\S]*?)\]/); assert.ok(m); const urls = [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map(x => x[1]); - assert.equal(urls.length, 5, - `GNOSIS_RPCS drifted from 5 entries; got ${urls.length}. ` + + assert.equal(urls.length, 4, + `GNOSIS_RPCS drifted from 4 entries; got ${urls.length}. ` + `Compare against rpc-config.test.mjs canonical list.`); }); diff --git a/auto-qa/tests/dead-references.test.mjs b/auto-qa/tests/dead-references.test.mjs index 9079815..995cdc4 100644 --- a/auto-qa/tests/dead-references.test.mjs +++ b/auto-qa/tests/dead-references.test.mjs @@ -68,13 +68,13 @@ test('PR #43 — no tickspread.com URLs remain in src/', () => { }); // ──────────────────────────────────────────────────────────────────────── -// PR #47 — Remove dead Supabase code (cleanup is PARTIAL) +// PR #47 — Remove dead Supabase code (cleanup COMPLETE as of PR #90) // // Baseline = the count of imports as of this iteration. If new ones get // added: test fails (regression). If real-fix work removes some: test // fails (forces the baseline to be lowered, ratcheting the cleanup). // ──────────────────────────────────────────────────────────────────────── -const SUPABASE_IMPORT_BASELINE = 10; +const SUPABASE_IMPORT_BASELINE = 0; test('PR #47 — supabase import count matches baseline', () => { const hits = findHits("from\\s+['\"]@supabase/supabase-js['\"]");