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['\"]");
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 */}
-
-
-
-
-
-
- Outcome
- Amount
- Price
- Date
-
-
-
-
-
-
-
-
- {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;