diff --git a/frontend/src/routes/(app)/(nns)/tokens/+page.svelte b/frontend/src/routes/(app)/(nns)/tokens/+page.svelte index 641154d7557..4e585f63bd4 100644 --- a/frontend/src/routes/(app)/(nns)/tokens/+page.svelte +++ b/frontend/src/routes/(app)/(nns)/tokens/+page.svelte @@ -48,13 +48,20 @@ import ImportTokenRemoveConfirmation from "$lib/components/accounts/ImportTokenRemoveConfirmation.svelte"; import { isUserTokenData } from "$lib/utils/user-token.utils"; import { removeImportedTokens } from "$lib/services/imported-tokens.services"; + import type { CanisterIdString } from "@dfinity/nns"; onMount(() => { loadCkBTCTokens(); }); - let loadSnsAccountsBalancesRequested = false; - let loadCkBTCAccountsBalancesRequested = false; + const loadedIcrcAccountsBalancesRequested = new Set(); + const getAndUpdateNotLoadedIds = (canisterIds: CanisterIdString[]) => { + const ids = canisterIds.filter( + (id) => !loadedIcrcAccountsBalancesRequested.has(id) + ); + ids.forEach((id) => loadedIcrcAccountsBalancesRequested.add(id)); + return ids; + }; const loadSnsAccountsBalances = async (projects: SnsFullProject[]) => { // We start when the projects are fetched @@ -62,22 +69,30 @@ return; } + const rootCanisterIds = projects.map(({ rootCanisterId }) => + rootCanisterId.toText() + ); + const notLoadedCanisterIds = getAndUpdateNotLoadedIds(rootCanisterIds); + // We trigger the loading of the Sns Accounts Balances only once - if (loadSnsAccountsBalancesRequested) { + if (notLoadedCanisterIds.length === 0) { return; } - loadSnsAccountsBalancesRequested = true; - await uncertifiedLoadSnsesAccountsBalances({ - rootCanisterIds: projects.map(({ rootCanisterId }) => rootCanisterId), + rootCanisterIds: notLoadedCanisterIds.map((id) => Principal.fromText(id)), excludeRootCanisterIds: [], }); }; const loadCkBTCAccountsBalances = async (universes: Universe[]) => { // We trigger the loading of the ckBTC Accounts Balances only once - if (loadCkBTCAccountsBalancesRequested) { + const canisterIds = universes.map( + (universe: Universe) => universe.canisterId + ); + const notLoadedCanisterIds = getAndUpdateNotLoadedIds(canisterIds); + + if (notLoadedCanisterIds.length === 0) { return; } @@ -88,6 +103,10 @@ * There is also a "Check for incoming BTC" button in the Wallet page. */ universes.forEach((universe: Universe) => { + if (!notLoadedCanisterIds.includes(universe.canisterId)) { + return; + } + const ckBTCCanisters = CKBTC_ADDITIONAL_CANISTERS[universe.canisterId]; if (nonNullish(ckBTCCanisters.minterCanisterId)) { updateBalance({ @@ -100,15 +119,19 @@ } }); - loadCkBTCAccountsBalancesRequested = true; - await loadAccountsBalances(universes.map(({ canisterId }) => canisterId)); }; const loadIcrcTokenAccounts = async ( icrcCanisters: IcrcCanistersStoreData ) => { - await loadAccountsBalances(Object.keys(icrcCanisters)); + const ids = Object.keys(icrcCanisters); + const notLoadedCanisterIds = getAndUpdateNotLoadedIds(ids); + // Skip loading if there are no new ids + if (notLoadedCanisterIds.length === 0) { + return; + } + await loadAccountsBalances(notLoadedCanisterIds); }; const loadAccountsBalances = async ( @@ -138,15 +161,15 @@ return loadAccountsBalances([universeId.toText()]); }; - $: (async () => { - if ($authSignedInStore) { - await Promise.allSettled([ - loadSnsAccountsBalances($snsProjectsCommittedStore), - loadCkBTCAccountsBalances($ckBTCUniversesStore), - loadIcrcTokenAccounts($icrcCanistersStore), - ]); - } - })(); + $: if ($authSignedInStore) { + loadSnsAccountsBalances($snsProjectsCommittedStore); + } + $: if ($authSignedInStore) { + loadCkBTCAccountsBalances($ckBTCUniversesStore); + } + $: if ($authSignedInStore) { + loadIcrcTokenAccounts($icrcCanistersStore); + } type ModalType = | "sns-send" diff --git a/frontend/src/tests/routes/app/tokens/page.spec.ts b/frontend/src/tests/routes/app/tokens/page.spec.ts index 8657e210579..36a015fa9bc 100644 --- a/frontend/src/tests/routes/app/tokens/page.spec.ts +++ b/frontend/src/tests/routes/app/tokens/page.spec.ts @@ -898,6 +898,40 @@ describe("Tokens route", () => { "Pacman", ]); }); + + it("should not reload balances after an imported token becomes failed", async () => { + const po = await renderPage(); + const rows = await po.getTokensPagePo().getTokensTable().getRows(); + const notFailedTokenCount = 8; + expect( + ( + await Promise.all( + rows.map( + async (row) => + !(await row.getFailedTokenTooltipPo().isPresent()) + ) + ) + ).filter(Boolean).length + ).toEqual(notFailedTokenCount); + + await runResolvedPromises(); + expect(icrcLedgerApi.queryIcrcBalance).toBeCalledTimes( + notFailedTokenCount + ); + + // Add a failed token + expect( + get(failedImportedTokenLedgerIdsStore).includes( + importedToken2Id.toText() + ) + ).toEqual(false); + failedImportedTokenLedgerIdsStore.add(importedToken2Id.toText()); + + await runResolvedPromises(); + expect(icrcLedgerApi.queryIcrcBalance).toBeCalledTimes( + notFailedTokenCount + ); + }); }); describe("when logged out", () => {