diff --git a/.env.example b/.env.example index 17cbc71c..9b4dcd16 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,4 @@ VITE_SWAP_BUTTON_SWITCH="true" VITE_PAYMASTER_URL="http://localhost:5050" VITE_SENTRY_ENVIRONMENT="staging" VITE_PULSE_NODE_URL="https://pulse.etherspot.io" +VITE_MOBULA_API_KEY="your_api_key_here" diff --git a/src/apps/insights/utils/logoUtils.ts b/src/apps/insights/utils/logoUtils.ts index 40361e6c..3339cf47 100644 --- a/src/apps/insights/utils/logoUtils.ts +++ b/src/apps/insights/utils/logoUtils.ts @@ -17,10 +17,10 @@ export const loadLogoCache = (): Record => { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) return {}; - + const cache: Record = JSON.parse(cached); const now = Date.now(); - + // Prune expired entries const pruned: Record = {}; Object.entries(cache).forEach(([symbol, entry]) => { @@ -29,7 +29,7 @@ export const loadLogoCache = (): Record => { pruned[symbol] = entry; } }); - + return pruned; } catch (err) { console.error('Error loading logo cache:', err); @@ -45,13 +45,16 @@ export const saveLogoCache = (cache: Record) => { } }; -export const getCachedLogo = (symbol: string, cache: Record): string | null => { +export const getCachedLogo = ( + symbol: string, + cache: Record +): string | null => { const entry = cache[symbol]; if (!entry) return null; - + const ttl = entry.miss ? MISS_TTL_MS : HIT_TTL_MS; if (Date.now() - entry.updatedAt >= ttl) return null; - + if (entry.miss) return null; // Don't return misses as valid URLs return entry.url || null; }; @@ -59,58 +62,73 @@ export const getCachedLogo = (symbol: string, cache: Record => { +export const fetchLogoFromMobula = async ( + symbol: string +): Promise => { try { console.log(`🔍 Querying Mobula for ${symbol}`); const response = await fetch( - `https://explorer-api.mobula.io/api/1/search?mode=og&sortBy=volume_24h&input=${symbol}` + `https://api.mobula.io/api/1/search?mode=og&sortBy=volume_24h&input=${symbol}`, + { + method: 'GET', + headers: { + Authorization: `${import.meta.env.VITE_MOBULA_API_KEY || 'your_api_key_here'}`, + }, + } ); const data = await response.json(); - + if (data?.data?.length > 0) { console.log(`📊 ${symbol} results: ${data.data.length} items`); - + // Selection strategy: prefer exact symbol match with highest market_cap let selectedToken = null; - + // First, try exact symbol match (case-insensitive) const exactMatches = data.data.filter( (token: any) => token.symbol?.toUpperCase() === symbol ); - + if (exactMatches.length > 0) { // Pick highest market_cap, fallback to liquidity, then volume selectedToken = exactMatches.reduce((best: any, current: any) => { - const bestScore = best.market_cap || best.liquidity || best.volume || 0; - const currentScore = current.market_cap || current.liquidity || current.volume || 0; + const bestScore = + best.market_cap || best.liquidity || best.volume || 0; + const currentScore = + current.market_cap || current.liquidity || current.volume || 0; return currentScore > bestScore ? current : best; }); - console.log(`✓ ${symbol} exact match: ${selectedToken.name} (${selectedToken.symbol}) - market_cap: ${selectedToken.market_cap}`); + console.log( + `✓ ${symbol} exact match: ${selectedToken.name} (${selectedToken.symbol}) - market_cap: ${selectedToken.market_cap}` + ); } else { // Fallback: pick item with highest market_cap that has a logo selectedToken = data.data .filter((token: any) => token.logo) .reduce((best: any, current: any) => { if (!best) return current; - const bestScore = best.market_cap || best.liquidity || best.volume || 0; - const currentScore = current.market_cap || current.liquidity || current.volume || 0; + const bestScore = + best.market_cap || best.liquidity || best.volume || 0; + const currentScore = + current.market_cap || current.liquidity || current.volume || 0; return currentScore > bestScore ? current : best; }, null); - + if (selectedToken) { - console.log(`⚠️ ${symbol} fallback match: ${selectedToken.name} (${selectedToken.symbol})`); + console.log( + `⚠️ ${symbol} fallback match: ${selectedToken.name} (${selectedToken.symbol})` + ); } } - + if (selectedToken?.logo) { return selectedToken.logo; } } - + return null; } catch (err) { console.error(`❌ Failed to fetch logo for ${symbol}:`, err); return null; } }; - diff --git a/src/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/LeftColumnTokenMarketDataRow.test.tsx.snap b/src/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/LeftColumnTokenMarketDataRow.test.tsx.snap index 8bae6cfc..b1b5d702 100644 --- a/src/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/LeftColumnTokenMarketDataRow.test.tsx.snap +++ b/src/apps/pillarx-app/components/TokenMarketDataRow/tests/__snapshots__/LeftColumnTokenMarketDataRow.test.tsx.snap @@ -41,7 +41,7 @@ exports[` - ETH token row > renders and matches

- 10mo ago + 1year ago

- ETH token row > renders and matches

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago

> renders and matches snapshot 1`] = `

- 10mo ago + 1year ago