@@ -9,6 +9,12 @@ import { NFT_FALLBACK_IMAGE } from 'utils/nftFallback';
99// Add a simple LRU cap to prevent unbounded growth
1010const MAX_CACHE_ENTRIES = 200 ;
1111const tokenImageCache = new Map < string , string | null > ( ) ;
12+ // Track when a cache key last failed so we can retry later
13+ const tokenImageFailureTs = new Map < string , number > ( ) ;
14+ // Retry failed icon fetches after a short cooldown (prevents permanent "stuck" fallbacks)
15+ const FAILURE_RETRY_MS = 5 * 60 * 1000 ; // 5 minutes
16+ // If an image is too large to safely base64-cache, fall back to rendering the URL directly
17+ const MAX_CACHED_BLOB_BYTES = 256 * 1024 ; // 256KB
1218
1319// Track in-flight requests to prevent duplicate fetches
1420const pendingTokenRequests = new Map < string , Promise < string | null > > ( ) ;
@@ -70,6 +76,26 @@ export const TokenIcon: React.FC<ITokenIconProps> = React.memo(
7076 // Check cache first
7177 if ( tokenImageCache . has ( cacheKey ) ) {
7278 const cached = tokenImageCache . get ( cacheKey ) ;
79+ // If we previously cached a failure (null), allow retry after cooldown
80+ if ( cached === null ) {
81+ const failedAt = tokenImageFailureTs . get ( cacheKey ) || 0 ;
82+ if ( failedAt && Date . now ( ) - failedAt >= FAILURE_RETRY_MS ) {
83+ tokenImageCache . delete ( cacheKey ) ;
84+ tokenImageFailureTs . delete ( cacheKey ) ;
85+ } else {
86+ return {
87+ url : null ,
88+ error : true ,
89+ loading : false ,
90+ } ;
91+ }
92+ } else {
93+ return {
94+ url : cached ,
95+ error : false ,
96+ loading : false ,
97+ } ;
98+ }
7399 return {
74100 url : cached ,
75101 error : cached === null ,
@@ -136,10 +162,18 @@ export const TokenIcon: React.FC<ITokenIconProps> = React.memo(
136162 if ( ! response . ok ) throw new Error ( 'Failed to fetch' ) ;
137163
138164 const blob = await response . blob ( ) ;
139- // Skip caching very large images to avoid memory bloat (~256KB)
140- if ( blob . size > 256 * 1024 ) {
141- tokenImageCache . set ( cacheKey , null ) ;
142- return null ;
165+ // If it's too large for base64 caching, just cache the URL so it can still render reliably
166+ if ( blob . size > MAX_CACHED_BLOB_BYTES ) {
167+ // Enforce LRU cap
168+ if ( tokenImageCache . size >= MAX_CACHE_ENTRIES ) {
169+ const oldestKey = tokenImageCache . keys ( ) . next ( ) . value as
170+ | string
171+ | undefined ;
172+ if ( oldestKey !== undefined ) tokenImageCache . delete ( oldestKey ) ;
173+ }
174+ tokenImageCache . set ( cacheKey , logo ) ;
175+ tokenImageFailureTs . delete ( cacheKey ) ;
176+ return logo ;
143177 }
144178 const reader = new FileReader ( ) ;
145179
@@ -154,6 +188,7 @@ export const TokenIcon: React.FC<ITokenIconProps> = React.memo(
154188 if ( oldestKey !== undefined ) tokenImageCache . delete ( oldestKey ) ;
155189 }
156190 tokenImageCache . set ( cacheKey , dataUrl ) ;
191+ tokenImageFailureTs . delete ( cacheKey ) ;
157192 resolve ( dataUrl ) ;
158193 } ;
159194
@@ -165,6 +200,7 @@ export const TokenIcon: React.FC<ITokenIconProps> = React.memo(
165200 if ( oldestKey !== undefined ) tokenImageCache . delete ( oldestKey ) ;
166201 }
167202 tokenImageCache . set ( cacheKey , null ) ;
203+ tokenImageFailureTs . set ( cacheKey , Date . now ( ) ) ;
168204 resolve ( null ) ;
169205 } ;
170206
@@ -173,6 +209,7 @@ export const TokenIcon: React.FC<ITokenIconProps> = React.memo(
173209 } catch ( err ) {
174210 // Cache the failure to prevent repeated attempts
175211 tokenImageCache . set ( cacheKey , null ) ;
212+ tokenImageFailureTs . set ( cacheKey , Date . now ( ) ) ;
176213 return null ;
177214 }
178215 } ;
0 commit comments