@@ -63,6 +63,15 @@ const BALANCE_FETCH_MAX_RETRIES = 3;
6363 */
6464const pendingBalanceRequests = new Map ( ) ;
6565
66+ /**
67+ * Map to track pending history fetch requests per tokenId.
68+ * This enables request deduplication and force upgrade capability.
69+ *
70+ * Structure: Map<tokenId, { force: boolean }>
71+ * - force: whether the pending request should force a fresh fetch
72+ */
73+ const pendingHistoryRequests = new Map ( ) ;
74+
6675/**
6776 * This saga will create a channel to queue TOKEN_FETCH_BALANCE_REQUESTED actions and
6877 * consumers that will run in parallel consuming those actions.
@@ -208,8 +217,83 @@ function* fetchTokenBalance(action) {
208217 }
209218}
210219
220+ /**
221+ * This saga will create a channel to queue TOKEN_FETCH_HISTORY_REQUESTED actions and
222+ * consumers that will run in parallel consuming those actions.
223+ *
224+ * 1. Request deduplication: Only one fetch per tokenId at a time
225+ * 2. Force upgrade: If a force=true request arrives while a force=false is pending,
226+ * the pending request is upgraded to force=true
227+ * 3. Race condition prevention: Prevents multiple consumers from processing the same tokenId
228+ */
229+ function * fetchTokenHistoryQueue ( ) {
230+ const fetchTokenHistoryChannel = yield call ( channel ) ;
231+
232+ // Fork CONCURRENT_FETCH_REQUESTS threads to download token history
233+ for ( let i = 0 ; i < CONCURRENT_FETCH_REQUESTS ; i += 1 ) {
234+ yield fork ( fetchTokenHistoryConsumer , fetchTokenHistoryChannel ) ;
235+ }
236+
237+ while ( true ) {
238+ const action = yield take ( types . TOKEN_FETCH_HISTORY_REQUESTED ) ;
239+ const { tokenId, force } = action ;
240+
241+ // Check if there's already a pending request for this tokenId
242+ if ( pendingHistoryRequests . has ( tokenId ) ) {
243+ const pending = pendingHistoryRequests . get ( tokenId ) ;
244+
245+ // Upgrade to force=true if the new request has force=true
246+ if ( force && ! pending . force ) {
247+ log . debug ( `Upgrading pending history request for ${ tokenId } to force=true` ) ;
248+ pending . force = true ;
249+ }
250+
251+ // Skip queueing duplicate request - the existing one will handle it
252+ log . debug ( `Skipping duplicate history request for ${ tokenId } , pending request exists` ) ;
253+ continue ;
254+ }
255+
256+ // Create new pending entry and queue the request
257+ pendingHistoryRequests . set ( tokenId , { force } ) ;
258+ yield put ( fetchTokenHistoryChannel , action ) ;
259+ }
260+ }
261+
262+ /**
263+ * This saga will consume the fetchTokenHistoryChannel for TOKEN_FETCH_HISTORY_REQUEST actions
264+ * and wait until the TOKEN_FETCH_HISTORY_SUCCESS action is dispatched with the specific tokenId
265+ */
266+ function * fetchTokenHistoryConsumer ( fetchTokenHistoryChannel ) {
267+ while ( true ) {
268+ const action = yield take ( fetchTokenHistoryChannel ) ;
269+
270+ yield fork ( fetchTokenHistory , action ) ;
271+ // Wait until the success action is dispatched before consuming another action
272+ yield take (
273+ specificTypeAndPayload ( [
274+ types . TOKEN_FETCH_HISTORY_SUCCESS ,
275+ types . TOKEN_FETCH_HISTORY_FAILED ,
276+ ] , {
277+ tokenId : action . tokenId ,
278+ } ) ,
279+ ) ;
280+ }
281+ }
282+
283+ /**
284+ * Fetches the history for a specific token.
285+ *
286+ * 1. Checks pendingHistoryRequests for the latest force value (supports force upgrade)
287+ * 2. Properly cleans up pending request tracking on completion
288+ *
289+ * @param {Object } action - The action containing tokenId and force flag
290+ */
211291function * fetchTokenHistory ( action ) {
212- const { tokenId, force } = action ;
292+ const { tokenId } = action ;
293+
294+ // Get the current force value from pending requests (may have been upgraded)
295+ const pendingRequest = pendingHistoryRequests . get ( tokenId ) ;
296+ const force = pendingRequest ?. force ?? action . force ;
213297
214298 try {
215299 const wallet = yield select ( ( state ) => state . wallet ) ;
@@ -230,6 +314,9 @@ function* fetchTokenHistory(action) {
230314 } catch ( e ) {
231315 log . error ( 'Error while fetching token history.' , e ) ;
232316 yield put ( tokenFetchHistoryFailed ( tokenId ) ) ;
317+ } finally {
318+ // Clean up pending request tracking
319+ pendingHistoryRequests . delete ( tokenId ) ;
233320 }
234321}
235322
@@ -374,8 +461,8 @@ export function* fetchTokenData(tokenId, force = false) {
374461export function * saga ( ) {
375462 yield all ( [
376463 fork ( fetchTokenBalanceQueue ) ,
464+ fork ( fetchTokenHistoryQueue ) ,
377465 fork ( fetchTokenMetadataQueue ) ,
378- takeEvery ( types . TOKEN_FETCH_HISTORY_REQUESTED , fetchTokenHistory ) ,
379466 takeEvery ( types . NEW_TOKEN , routeTokenChange ) ,
380467 takeEvery ( types . SET_TOKENS , routeTokenChange ) ,
381468 ] ) ;
0 commit comments