diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index f77f6c568cc..bd6918d5798 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -10,6 +10,7 @@ import type { ImportedTokens } from "$lib/canisters/nns-dapp/nns-dapp.types"; import { MAX_IMPORTED_TOKENS } from "$lib/constants/imported-tokens.constants"; import { FORCE_CALL_STRATEGY } from "$lib/constants/mockable.constants"; import { getAuthenticatedIdentity } from "$lib/services/auth.services"; +import { icrcCanistersStore } from "$lib/stores/icrc-canisters.store"; import { importedTokensStore } from "$lib/stores/imported-tokens.store"; import { toastsError, toastsSuccess } from "$lib/stores/toasts.store"; import type { ImportedTokenData } from "$lib/types/imported-tokens"; @@ -19,6 +20,7 @@ import { toImportedTokenData, } from "$lib/utils/imported-tokens.utils"; import { isNullish } from "@dfinity/utils"; +import { get } from "svelte/store"; import { queryAndUpdate } from "./utils.services"; /** Load imported tokens from the `nns-dapp` backend and update the `importedTokensStore` store. @@ -32,11 +34,31 @@ export const loadImportedTokens = async ({ return queryAndUpdate({ request: (options) => getImportedTokens(options), strategy: FORCE_CALL_STRATEGY, - onLoad: ({ response: { imported_tokens: importedTokens }, certified }) => + onLoad: ({ + response: { imported_tokens: rawImportedTokens }, + certified, + }) => { + const importedTokens = rawImportedTokens.map(toImportedTokenData); importedTokensStore.set({ - importedTokens: importedTokens.map(toImportedTokenData), + importedTokens, certified, - }), + }); + + if (!certified && notForceCallStrategy()) { + return; + } + + // Populate icrcCanistersStore with the imported tokens. + for (const { ledgerCanisterId, indexCanisterId } of importedTokens) { + // If the imported token is not already in the store, add it. + if (isNullish(get(icrcCanistersStore)[ledgerCanisterId.toText()])) { + icrcCanistersStore.setCanisters({ + ledgerCanisterId, + indexCanisterId, + }); + } + } + }, onError: ({ error: err, certified }) => { console.error(err); @@ -97,8 +119,6 @@ export const addImportedToken = async ({ tokenToAdd: ImportedTokenData; importedTokens: ImportedTokenData[]; }): Promise<{ success: boolean }> => { - // TODO: validate importedToken (not sns, not ck, is unique, etc.) - const tokens = [...importedTokens, tokenToAdd]; const { err } = await saveImportedToken({ tokens }); diff --git a/frontend/src/lib/stores/icrc-canisters.store.ts b/frontend/src/lib/stores/icrc-canisters.store.ts index fd71ca0ff31..85201addc8c 100644 --- a/frontend/src/lib/stores/icrc-canisters.store.ts +++ b/frontend/src/lib/stores/icrc-canisters.store.ts @@ -7,7 +7,7 @@ import { writable } from "svelte/store"; export interface IcrcCanisters { ledgerCanisterId: Principal; - indexCanisterId: Principal; + indexCanisterId: Principal | undefined; } export type IcrcCanistersStoreData = Record< diff --git a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts index a91b35a19f3..6fbda730a71 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -9,12 +9,14 @@ import { loadImportedTokens, removeImportedTokens, } from "$lib/services/imported-tokens.services"; +import { icrcCanistersStore } from "$lib/stores/icrc-canisters.store"; import { importedTokensStore } from "$lib/stores/imported-tokens.store"; import * as toastsStore from "$lib/stores/toasts.store"; import type { ImportedTokenData } from "$lib/types/imported-tokens"; import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock"; import { principal } from "$tests/mocks/sns-projects.mock"; import { get } from "svelte/store"; +import { runResolvedPromises } from "../../utils/timers.test-utils"; describe("imported-tokens-services", () => { const importedTokenA: ImportedToken = { @@ -39,6 +41,7 @@ describe("imported-tokens-services", () => { vi.clearAllMocks(); resetIdentity(); importedTokensStore.reset(); + icrcCanistersStore.reset(); vi.spyOn(console, "error").mockReturnValue(); }); @@ -74,6 +77,38 @@ describe("imported-tokens-services", () => { }); }); + it("should add imported tokens to the icrcCanistersStore", async () => { + const spyGetImportedTokens = vi + .spyOn(importedTokensApi, "getImportedTokens") + .mockResolvedValueOnce({ + imported_tokens: [importedTokenA, importedTokenB], + }); + + expect(spyGetImportedTokens).toBeCalledTimes(0); + expect(get(icrcCanistersStore)).toEqual({}); + + expect(get(importedTokensStore)).toEqual({ + importedTokens: undefined, + certified: undefined, + }); + + await loadImportedTokens(); + // Wait for the store to update. + await runResolvedPromises(); + + expect(spyGetImportedTokens).toBeCalledTimes(2); + expect(get(icrcCanistersStore)).toEqual({ + [importedTokenDataA.ledgerCanisterId.toText()]: { + ledgerCanisterId: importedTokenDataA.ledgerCanisterId, + indexCanisterId: importedTokenDataA.indexCanisterId, + }, + [importedTokenDataB.ledgerCanisterId.toText()]: { + ledgerCanisterId: importedTokenDataB.ledgerCanisterId, + indexCanisterId: importedTokenDataB.indexCanisterId, + }, + }); + }); + it("should display toast on error", async () => { const spyToastError = vi.spyOn(toastsStore, "toastsError"); const spyGetImportedTokens = vi