From 40488ecf0b63dcec32a6e5ac8c47f4d8de1fc227 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 10:44:07 +0200 Subject: [PATCH 001/161] New const MAX_IMPORTED_TOKENS --- frontend/src/lib/constants/imported-tokens.constants.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/src/lib/constants/imported-tokens.constants.ts diff --git a/frontend/src/lib/constants/imported-tokens.constants.ts b/frontend/src/lib/constants/imported-tokens.constants.ts new file mode 100644 index 00000000000..974a05277d6 --- /dev/null +++ b/frontend/src/lib/constants/imported-tokens.constants.ts @@ -0,0 +1 @@ +export const MAX_IMPORTED_TOKENS = 20; \ No newline at end of file From c79717e426439d97eb8db4c6a6819f0264591e07 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 10:50:36 +0200 Subject: [PATCH 002/161] New imported tokens services --- frontend/src/lib/i18n/en.json | 6 +- .../lib/services/imported-tokens.services.ts | 92 +++++++++++++++++++ frontend/src/lib/types/i18n.d.ts | 3 + 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 frontend/src/lib/services/imported-tokens.services.ts diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index f61e109fbee..77117030bad 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -847,7 +847,8 @@ "get_exchange_rate": "Error getting the exchange rate of ICP to Cycles." }, "error__imported_tokens": { - "too_many": "You can't import more than $limit tokens." + "too_many": "You can't import more than $limit tokens.", + "load_imported_tokens": "There was an unexpected issue while loading imported tokens." }, "error__sns": { "undefined_project": "The requested project is invalid or throws an error.", @@ -1034,6 +1035,7 @@ "hide_zero_balances_toggle_label": "Switch between showing and hiding tokens with a balance of zero", "zero_balance_hidden": "Tokens with 0 balances are hidden.", "show_all": "Show all", - "import_token": "Import Token" + "import_token": "Import Token", + "add_imported_token_success": "New token has been successfully imported!" } } diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts new file mode 100644 index 00000000000..0fb9ceaa8df --- /dev/null +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -0,0 +1,92 @@ +import { + getImportedTokens, + setImportedTokens, +} from "$lib/api/imported-tokens.api"; +import { TooManyImportedTokensError } from "$lib/canisters/nns-dapp/nns-dapp.errors"; +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 { importedTokensStore } from "$lib/stores/imported-tokens.store"; +import { toastsError, toastsSuccess } from "$lib/stores/toasts.store"; +import type { ImportedTokenData } from "$lib/types/imported-tokens"; +import { notForceCallStrategy } from "$lib/utils/env.utils"; +import { + fromImportedTokenData, + toImportedTokenData, +} from "$lib/utils/imported-tokens.utils"; +import { queryAndUpdate } from "./utils.services"; + +export const loadImportedTokens = async () => { + return queryAndUpdate({ + request: (options) => getImportedTokens(options), + strategy: FORCE_CALL_STRATEGY, + onLoad: ({ response: { imported_tokens: importedTokens }, certified }) => + importedTokensStore.set({ + importedTokens: importedTokens.map(toImportedTokenData), + certified, + }), + onError: ({ error: err, certified }) => { + console.error(err); + + if (!certified && notForceCallStrategy()) { + return; + } + + // Explicitly handle only UPDATE errors + importedTokensStore.reset(); + + toastsError({ + labelKey: "error.load_imported_tokens", + err, + }); + }, + logMessage: "Get Imported Tokens", + }); +}; + +export const addImportedToken = async ( + tokenToAdd: ImportedTokenData, + importedTokens: ImportedTokenData[] +) => { + // TODO: validate importedToken (not sns, not ck, is unique, etc.) + + const { success } = await saveImportedTokens([...importedTokens, tokenToAdd]); + if (success) { + await loadImportedTokens(); + + toastsSuccess({ + labelKey: "tokens.add_imported_token_success", + }); + } + + return { success }; +}; + +const saveImportedTokens = async ( + importedTokens: ImportedTokenData[] +): Promise<{ success: boolean }> => { + try { + const identity = await getAuthenticatedIdentity(); + await setImportedTokens({ + identity, + importedTokens: importedTokens.map(fromImportedTokenData), + }); + } catch (err: unknown) { + if (err instanceof TooManyImportedTokensError) { + toastsError({ + labelKey: "error__ckbtc.too_many", + substitutions: { $limit: `${MAX_IMPORTED_TOKENS}` }, + }); + } else { + toastsError({ + labelKey: "error.add_imported_token", + err, + }); + } + + return { success: false }; + } + + return { success: true }; +}; diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index d68a7d9a418..20535df8c61 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -888,6 +888,8 @@ interface I18nError__canister { interface I18nError__imported_tokens { too_many: string; + load_imported_tokens: string; + add_imported_token: string; } interface I18nError__sns { @@ -1095,6 +1097,7 @@ interface I18nTokens { zero_balance_hidden: string; show_all: string; import_token: string; + add_imported_token_success: string; } interface I18nNeuron_state { From da6ad8f6f7184eccc003b963b57f8b9f346d0222 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 10:58:40 +0200 Subject: [PATCH 003/161] Comments --- frontend/src/lib/constants/imported-tokens.constants.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/constants/imported-tokens.constants.ts b/frontend/src/lib/constants/imported-tokens.constants.ts index 974a05277d6..90fc22d529f 100644 --- a/frontend/src/lib/constants/imported-tokens.constants.ts +++ b/frontend/src/lib/constants/imported-tokens.constants.ts @@ -1 +1,3 @@ -export const MAX_IMPORTED_TOKENS = 20; \ No newline at end of file +/// Maximum number of tokens that can be imported by single user. +/// Should be in sync with the [nns-dapp backend value](https://github.com/dfinity/nns-dapp/blob/929e7d89a2b884b2b6df5d51dbaa37bd0d77eeed/rs/backend/src/accounts_store.rs#L42) +export const MAX_IMPORTED_TOKENS = 20; From 503c2943d7f3224d91da21cf7a68578def2ac856 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 11:01:09 +0200 Subject: [PATCH 004/161] refactro: addImportedToken --- .../lib/services/imported-tokens.services.ts | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index 0fb9ceaa8df..d70134d4abe 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -47,31 +47,20 @@ export const loadImportedTokens = async () => { export const addImportedToken = async ( tokenToAdd: ImportedTokenData, - importedTokens: ImportedTokenData[] + tokens: ImportedTokenData[] ) => { // TODO: validate importedToken (not sns, not ck, is unique, etc.) - const { success } = await saveImportedTokens([...importedTokens, tokenToAdd]); - if (success) { + try { + const identity = await getAuthenticatedIdentity(); + const importedTokens = [...tokens, tokenToAdd].map(fromImportedTokenData); + + await setImportedTokens({ identity, importedTokens }); await loadImportedTokens(); toastsSuccess({ labelKey: "tokens.add_imported_token_success", }); - } - - return { success }; -}; - -const saveImportedTokens = async ( - importedTokens: ImportedTokenData[] -): Promise<{ success: boolean }> => { - try { - const identity = await getAuthenticatedIdentity(); - await setImportedTokens({ - identity, - importedTokens: importedTokens.map(fromImportedTokenData), - }); } catch (err: unknown) { if (err instanceof TooManyImportedTokensError) { toastsError({ From ef1593be5f5f8d14cbe58fb7004be5ecf4f60ab7 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 13:44:43 +0200 Subject: [PATCH 005/161] Test loadImportedTokens --- .../lib/services/imported-tokens.services.ts | 1 - .../services/imported-tokens.services.spec.ts | 108 ++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 frontend/src/tests/lib/services/imported-tokens.services.spec.ts diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index d70134d4abe..cabae0adab5 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -38,7 +38,6 @@ export const loadImportedTokens = async () => { toastsError({ labelKey: "error.load_imported_tokens", - err, }); }, logMessage: "Get Imported Tokens", diff --git a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts new file mode 100644 index 00000000000..a4caf732758 --- /dev/null +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -0,0 +1,108 @@ +import * as importedTokensApi from "$lib/api/imported-tokens.api"; +import type { ImportedToken } from "$lib/canisters/nns-dapp/nns-dapp.types"; +import { loadImportedTokens } from "$lib/services/imported-tokens.services"; +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"; + +describe("imported-tokens-services", () => { + const importedTokenA: ImportedToken = { + ledger_canister_id: principal(0), + index_canister_id: [principal(1)], + }; + const importedTokenDataA: ImportedTokenData = { + ledgerCanisterId: principal(0), + indexCanisterId: principal(1), + }; + const importedTokenB: ImportedToken = { + ledger_canister_id: principal(2), + index_canister_id: [], + }; + const importedTokenDataB: ImportedTokenData = { + ledgerCanisterId: principal(2), + indexCanisterId: undefined, + }; + + beforeEach(() => { + vi.clearAllMocks(); + resetIdentity(); + importedTokensStore.reset(); + vi.spyOn(console, "error").mockReturnValue(); + }); + + describe("loadImportedTokens", () => { + it("should call getImportedTokens and load imported tokens in store", async () => { + const spyGetImportedTokens = vi + .spyOn(importedTokensApi, "getImportedTokens") + .mockResolvedValue({ + imported_tokens: [importedTokenA, importedTokenB], + }); + + expect(spyGetImportedTokens).toBeCalledTimes(0); + + expect(get(importedTokensStore)).toEqual({ + importedTokens: undefined, + certified: undefined, + }); + + await loadImportedTokens(); + + expect(spyGetImportedTokens).toBeCalledTimes(2); + expect(spyGetImportedTokens).toHaveBeenCalledWith({ + certified: false, + identity: mockIdentity, + }); + expect(spyGetImportedTokens).toHaveBeenCalledWith({ + certified: true, + identity: mockIdentity, + }); + expect(get(importedTokensStore)).toEqual({ + importedTokens: [importedTokenDataA, importedTokenDataB], + certified: true, + }); + }); + + it("should display toast on error", async () => { + const spyToastError = vi.spyOn(toastsStore, "toastsError"); + const spyGetImportedTokens = vi + .spyOn(importedTokensApi, "getImportedTokens") + .mockRejectedValue(new Error("test")); + + expect(spyGetImportedTokens).toBeCalledTimes(0); + expect(spyToastError).not.toBeCalled(); + + await loadImportedTokens(); + + expect(spyToastError).toBeCalledTimes(1); + expect(spyToastError).toBeCalledWith({ + labelKey: "error.load_imported_tokens", + }); + }); + + it("should reset store on error", async () => { + const spyGetImportedTokens = vi + .spyOn(importedTokensApi, "getImportedTokens") + .mockRejectedValue(new Error("test")); + + importedTokensStore.set({ + importedTokens: [importedTokenDataA], + certified: true, + }); + + expect(get(importedTokensStore)).toEqual({ + importedTokens: [importedTokenDataA], + certified: true, + }); + + await loadImportedTokens(); + + expect(get(importedTokensStore)).toEqual({ + importedTokens: undefined, + certified: undefined, + }); + }); + }); +}); From 015346f73fe8de249f5a5def3b0d18a96daf1b34 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 20:14:56 +0200 Subject: [PATCH 006/161] refactor: error messages --- frontend/src/lib/i18n/en.json | 8 ++++---- .../lib/services/imported-tokens.services.ts | 18 +++++++++++------- frontend/src/lib/types/i18n.d.ts | 5 ++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 77117030bad..c3026f87a3d 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -847,8 +847,9 @@ "get_exchange_rate": "Error getting the exchange rate of ICP to Cycles." }, "error__imported_tokens": { - "too_many": "You can't import more than $limit tokens.", - "load_imported_tokens": "There was an unexpected issue while loading imported tokens." + "load_imported_tokens": "There was an unexpected issue while loading imported tokens.", + "add_imported_token_success": "New token has been successfully imported!", + "too_many": "You can't import more than $limit tokens." }, "error__sns": { "undefined_project": "The requested project is invalid or throws an error.", @@ -1035,7 +1036,6 @@ "hide_zero_balances_toggle_label": "Switch between showing and hiding tokens with a balance of zero", "zero_balance_hidden": "Tokens with 0 balances are hidden.", "show_all": "Show all", - "import_token": "Import Token", - "add_imported_token_success": "New token has been successfully imported!" + "import_token": "Import Token" } } diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index cabae0adab5..0971632de3a 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -37,17 +37,21 @@ export const loadImportedTokens = async () => { importedTokensStore.reset(); toastsError({ - labelKey: "error.load_imported_tokens", + labelKey: "error__imported_tokens.load_imported_tokens", + err, }); }, logMessage: "Get Imported Tokens", }); }; -export const addImportedToken = async ( - tokenToAdd: ImportedTokenData, - tokens: ImportedTokenData[] -) => { +export const addImportedToken = async ({ + tokenToAdd, + tokens, +}: { + tokenToAdd: ImportedTokenData; + tokens: ImportedTokenData[]; +}) => { // TODO: validate importedToken (not sns, not ck, is unique, etc.) try { @@ -63,12 +67,12 @@ export const addImportedToken = async ( } catch (err: unknown) { if (err instanceof TooManyImportedTokensError) { toastsError({ - labelKey: "error__ckbtc.too_many", + labelKey: "error__imported_tokens.too_many", substitutions: { $limit: `${MAX_IMPORTED_TOKENS}` }, }); } else { toastsError({ - labelKey: "error.add_imported_token", + labelKey: "error__imported_tokens.add_imported_token", err, }); } diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 20535df8c61..e077bc136ec 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -887,9 +887,9 @@ interface I18nError__canister { } interface I18nError__imported_tokens { - too_many: string; load_imported_tokens: string; - add_imported_token: string; + add_imported_token_success: string; + too_many: string; } interface I18nError__sns { @@ -1097,7 +1097,6 @@ interface I18nTokens { zero_balance_hidden: string; show_all: string; import_token: string; - add_imported_token_success: string; } interface I18nNeuron_state { From 3d49dee8160208a36238b738067cbcaa5cbe00c4 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 20:16:35 +0200 Subject: [PATCH 007/161] refactor: testError --- .../lib/services/imported-tokens.services.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 a4caf732758..50df1155f2d 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -25,6 +25,7 @@ describe("imported-tokens-services", () => { ledgerCanisterId: principal(2), indexCanisterId: undefined, }; + const testError = new Error("test"); beforeEach(() => { vi.clearAllMocks(); @@ -69,7 +70,7 @@ describe("imported-tokens-services", () => { const spyToastError = vi.spyOn(toastsStore, "toastsError"); const spyGetImportedTokens = vi .spyOn(importedTokensApi, "getImportedTokens") - .mockRejectedValue(new Error("test")); + .mockRejectedValue(testError); expect(spyGetImportedTokens).toBeCalledTimes(0); expect(spyToastError).not.toBeCalled(); @@ -78,14 +79,15 @@ describe("imported-tokens-services", () => { expect(spyToastError).toBeCalledTimes(1); expect(spyToastError).toBeCalledWith({ - labelKey: "error.load_imported_tokens", + labelKey: "error__imported_tokens.load_imported_tokens", + err: testError, }); }); it("should reset store on error", async () => { - const spyGetImportedTokens = vi - .spyOn(importedTokensApi, "getImportedTokens") - .mockRejectedValue(new Error("test")); + vi.spyOn(importedTokensApi, "getImportedTokens").mockRejectedValue( + testError + ); importedTokensStore.set({ importedTokens: [importedTokenDataA], From 1e36698bf96cfb3e5526706c39ca5bd3e6e2172f Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 20:16:45 +0200 Subject: [PATCH 008/161] Test addImportedToken --- .../services/imported-tokens.services.spec.ts | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) 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 50df1155f2d..517a9a812a0 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -1,6 +1,10 @@ import * as importedTokensApi from "$lib/api/imported-tokens.api"; +import { TooManyImportedTokensError } from "$lib/canisters/nns-dapp/nns-dapp.errors"; import type { ImportedToken } from "$lib/canisters/nns-dapp/nns-dapp.types"; -import { loadImportedTokens } from "$lib/services/imported-tokens.services"; +import { + addImportedToken, + loadImportedTokens, +} from "$lib/services/imported-tokens.services"; import { importedTokensStore } from "$lib/stores/imported-tokens.store"; import * as toastsStore from "$lib/stores/toasts.store"; import type { ImportedTokenData } from "$lib/types/imported-tokens"; @@ -107,4 +111,96 @@ describe("imported-tokens-services", () => { }); }); }); + + describe("addImportedToken", () => { + it("should call setImportedTokens with updated token list", async () => { + const spySetImportedTokens = vi + .spyOn(importedTokensApi, "setImportedTokens") + .mockResolvedValue(undefined); + expect(spySetImportedTokens).toBeCalledTimes(0); + + const { success } = await addImportedToken({ + tokenToAdd: importedTokenDataB, + tokens: [importedTokenDataA], + }); + + expect(success).toEqual(true); + expect(spySetImportedTokens).toBeCalledTimes(1); + expect(spySetImportedTokens).toHaveBeenCalledWith({ + identity: mockIdentity, + importedTokens: [importedTokenA, importedTokenB], + }); + }); + + it("should update the store", async () => { + const spyGetImportedTokens = vi + .spyOn(importedTokensApi, "getImportedTokens") + .mockResolvedValue({ + imported_tokens: [importedTokenA, importedTokenB], + }); + vi.spyOn(importedTokensApi, "setImportedTokens").mockResolvedValue( + undefined + ); + importedTokensStore.set({ + importedTokens: [importedTokenDataA], + certified: true, + }); + expect(spyGetImportedTokens).toBeCalledTimes(0); + expect(get(importedTokensStore)).toEqual({ + importedTokens: [importedTokenDataA], + certified: true, + }); + + await addImportedToken({ + tokenToAdd: importedTokenDataB, + tokens: [importedTokenDataA], + }); + + expect(spyGetImportedTokens).toBeCalledTimes(2); + expect(get(importedTokensStore)).toEqual({ + importedTokens: [importedTokenDataA, importedTokenDataB], + certified: true, + }); + }); + + it("should display toast on error", async () => { + const spyToastError = vi.spyOn(toastsStore, "toastsError"); + vi.spyOn(importedTokensApi, "setImportedTokens").mockRejectedValue( + testError + ); + expect(spyToastError).not.toBeCalled(); + + const { success } = await addImportedToken({ + tokenToAdd: importedTokenDataB, + tokens: [importedTokenDataA], + }); + + expect(success).toEqual(false); + expect(spyToastError).toBeCalledTimes(1); + expect(spyToastError).toBeCalledWith({ + labelKey: "error__imported_tokens.add_imported_token", + err: testError, + }); + }); + + it("should handle too many tokens errors", async () => { + const spyToastError = vi.spyOn(toastsStore, "toastsError"); + vi.spyOn(importedTokensApi, "setImportedTokens").mockRejectedValue( + new TooManyImportedTokensError("too many tokens") + ); + expect(spyToastError).not.toBeCalled(); + + const { success } = await addImportedToken({ + tokenToAdd: importedTokenDataB, + tokens: [importedTokenDataA], + }); + + expect(success).toEqual(false); + expect(spyToastError).toBeCalledTimes(1); + expect(spyToastError).toBeCalledWith({ + labelKey: "error__imported_tokens.too_many", + substitutions: { $limit: "20" }, + }); + }); + }); }); From 09e3c30b0d283a83a50f59063a1bf282725240ed Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 20:33:12 +0200 Subject: [PATCH 009/161] refactor: split addImportedToken --- .../lib/services/imported-tokens.services.ts | 35 ++++++++++++++----- .../services/imported-tokens.services.spec.ts | 8 ++--- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index 0971632de3a..e98c6508cd1 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -45,21 +45,16 @@ export const loadImportedTokens = async () => { }); }; -export const addImportedToken = async ({ - tokenToAdd, +const saveImportedToken = async ({ tokens, }: { - tokenToAdd: ImportedTokenData; tokens: ImportedTokenData[]; -}) => { - // TODO: validate importedToken (not sns, not ck, is unique, etc.) - +}): Promise<{ success: boolean }> => { try { const identity = await getAuthenticatedIdentity(); - const importedTokens = [...tokens, tokenToAdd].map(fromImportedTokenData); + const importedTokens = tokens.map(fromImportedTokenData); await setImportedTokens({ identity, importedTokens }); - await loadImportedTokens(); toastsSuccess({ labelKey: "tokens.add_imported_token_success", @@ -82,3 +77,27 @@ export const addImportedToken = async ({ return { success: true }; }; + +export const addImportedToken = async ({ + tokenToAdd, + importedTokens, +}: { + tokenToAdd: ImportedTokenData; + importedTokens: ImportedTokenData[]; +}): Promise<{ success: boolean }> => { + // TODO: validate importedToken (not sns, not ck, is unique, etc.) + + const tokens = [...importedTokens, tokenToAdd]; + const { success } = await saveImportedToken({ tokens }); + + if (success) { + await loadImportedTokens(); + + toastsSuccess({ + labelKey: "tokens.add_imported_token_success", + }); + } + + return { success }; +}; + 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 517a9a812a0..04e632444af 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -121,7 +121,7 @@ describe("imported-tokens-services", () => { const { success } = await addImportedToken({ tokenToAdd: importedTokenDataB, - tokens: [importedTokenDataA], + importedTokens: [importedTokenDataA], }); expect(success).toEqual(true); @@ -153,7 +153,7 @@ describe("imported-tokens-services", () => { await addImportedToken({ tokenToAdd: importedTokenDataB, - tokens: [importedTokenDataA], + importedTokens: [importedTokenDataA], }); expect(spyGetImportedTokens).toBeCalledTimes(2); @@ -172,7 +172,7 @@ describe("imported-tokens-services", () => { const { success } = await addImportedToken({ tokenToAdd: importedTokenDataB, - tokens: [importedTokenDataA], + importedTokens: [importedTokenDataA], }); expect(success).toEqual(false); @@ -192,7 +192,7 @@ describe("imported-tokens-services", () => { const { success } = await addImportedToken({ tokenToAdd: importedTokenDataB, - tokens: [importedTokenDataA], + importedTokens: [importedTokenDataA], }); expect(success).toEqual(false); From 2740b475003bb99bcd9e51374d6c44c728d3d7a4 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 20:33:29 +0200 Subject: [PATCH 010/161] New removeImportedToken service --- .../lib/services/imported-tokens.services.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index e98c6508cd1..1efb8470c9e 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -101,3 +101,26 @@ export const addImportedToken = async ({ return { success }; }; +export const removeImportedToken = async ({ + tokenToRemove, + importedTokens, +}: { + tokenToRemove: ImportedTokenData; + importedTokens: ImportedTokenData[]; +}): Promise<{ success: boolean }> => { + const tokens = importedTokens.filter( + ({ ledgerCanisterId: id }) => + id.toText() !== tokenToRemove.ledgerCanisterId.toText() + ); + const { success } = await saveImportedToken({ tokens }); + + if (success) { + await loadImportedTokens(); + + toastsSuccess({ + labelKey: "tokens.remove_imported_token_success", + }); + } + + return { success }; +}; From 91c476d5db32125a620310996d7ef9d502fddc94 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 21:00:06 +0200 Subject: [PATCH 011/161] refactor: error/success messages --- frontend/src/lib/i18n/en.json | 7 ++- .../lib/services/imported-tokens.services.ts | 62 ++++++++++--------- frontend/src/lib/types/i18n.d.ts | 5 +- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index c3026f87a3d..7691b4c8b21 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -848,7 +848,8 @@ }, "error__imported_tokens": { "load_imported_tokens": "There was an unexpected issue while loading imported tokens.", - "add_imported_token_success": "New token has been successfully imported!", + "add_imported_token": "There was an unexpected issue while adding new imported token.", + "remove_imported_token": "There was an unexpected issue while adding new imported token.", "too_many": "You can't import more than $limit tokens." }, "error__sns": { @@ -1036,6 +1037,8 @@ "hide_zero_balances_toggle_label": "Switch between showing and hiding tokens with a balance of zero", "zero_balance_hidden": "Tokens with 0 balances are hidden.", "show_all": "Show all", - "import_token": "Import Token" + "import_token": "Import Token", + "add_imported_token_success": "New token has been successfully imported!", + "remove_imported_token_success": "The token has been successfully removed!" } } diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index 1efb8470c9e..df23ed1c4ad 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -15,6 +15,7 @@ import { fromImportedTokenData, toImportedTokenData, } from "$lib/utils/imported-tokens.utils"; +import { isNullish } from "@dfinity/utils"; import { queryAndUpdate } from "./utils.services"; export const loadImportedTokens = async () => { @@ -49,33 +50,16 @@ const saveImportedToken = async ({ tokens, }: { tokens: ImportedTokenData[]; -}): Promise<{ success: boolean }> => { +}): Promise<{ err: Error | undefined }> => { try { const identity = await getAuthenticatedIdentity(); const importedTokens = tokens.map(fromImportedTokenData); - await setImportedTokens({ identity, importedTokens }); - - toastsSuccess({ - labelKey: "tokens.add_imported_token_success", - }); - } catch (err: unknown) { - if (err instanceof TooManyImportedTokensError) { - toastsError({ - labelKey: "error__imported_tokens.too_many", - substitutions: { $limit: `${MAX_IMPORTED_TOKENS}` }, - }); - } else { - toastsError({ - labelKey: "error__imported_tokens.add_imported_token", - err, - }); - } - - return { success: false }; + } catch (err) { + return { err: err as Error }; } - return { success: true }; + return { err: undefined }; }; export const addImportedToken = async ({ @@ -88,17 +72,30 @@ export const addImportedToken = async ({ // TODO: validate importedToken (not sns, not ck, is unique, etc.) const tokens = [...importedTokens, tokenToAdd]; - const { success } = await saveImportedToken({ tokens }); + const { err } = await saveImportedToken({ tokens }); - if (success) { + if (isNullish(err)) { await loadImportedTokens(); - toastsSuccess({ labelKey: "tokens.add_imported_token_success", }); + + return { success: true }; } - return { success }; + if (err instanceof TooManyImportedTokensError) { + toastsError({ + labelKey: "error__imported_tokens.too_many", + substitutions: { $limit: `${MAX_IMPORTED_TOKENS}` }, + }); + } else { + toastsError({ + labelKey: "error__imported_tokens.add_imported_token", + err, + }); + } + + return { success: false }; }; export const removeImportedToken = async ({ @@ -108,19 +105,26 @@ export const removeImportedToken = async ({ tokenToRemove: ImportedTokenData; importedTokens: ImportedTokenData[]; }): Promise<{ success: boolean }> => { + // Compare imported tokens by their ledgerCanisterId because they should be unique. const tokens = importedTokens.filter( ({ ledgerCanisterId: id }) => id.toText() !== tokenToRemove.ledgerCanisterId.toText() ); - const { success } = await saveImportedToken({ tokens }); + const { err } = await saveImportedToken({ tokens }); - if (success) { + if (isNullish(err)) { await loadImportedTokens(); - toastsSuccess({ labelKey: "tokens.remove_imported_token_success", }); + + return { success: true }; } - return { success }; + toastsError({ + labelKey: "error__imported_tokens.remove_imported_token", + err, + }); + + return { success: false }; }; diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index e077bc136ec..5908d011372 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -888,7 +888,8 @@ interface I18nError__canister { interface I18nError__imported_tokens { load_imported_tokens: string; - add_imported_token_success: string; + add_imported_token: string; + remove_imported_token: string; too_many: string; } @@ -1097,6 +1098,8 @@ interface I18nTokens { zero_balance_hidden: string; show_all: string; import_token: string; + add_imported_token_success: string; + remove_imported_token_success: string; } interface I18nNeuron_state { From c913361c6741f9eda0c9172caa115d24022f1ba1 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 21:02:38 +0200 Subject: [PATCH 012/161] test success toast --- .../services/imported-tokens.services.spec.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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 04e632444af..58bd07c2918 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -163,6 +163,27 @@ describe("imported-tokens-services", () => { }); }); + it("should display success toast", async () => { + const spyToastSuccsess = vi.spyOn(toastsStore, "toastsSuccess"); + vi.spyOn(importedTokensApi, "setImportedTokens").mockRejectedValue( + undefined + ); + vi.spyOn(importedTokensApi, "getImportedTokens").mockResolvedValue({ + imported_tokens: [importedTokenA, importedTokenB], + }); + expect(spyToastSuccsess).not.toBeCalled(); + + await addImportedToken({ + tokenToAdd: importedTokenDataB, + importedTokens: [importedTokenDataA], + }); + + expect(spyToastSuccsess).toBeCalledTimes(1); + expect(spyToastSuccsess).toBeCalledWith({ + labelKey: "tokens.add_imported_token_success", + }); + }); + it("should display toast on error", async () => { const spyToastError = vi.spyOn(toastsStore, "toastsError"); vi.spyOn(importedTokensApi, "setImportedTokens").mockRejectedValue( From c679bdec018e2078a630980d523337cbb05bb6ef Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 21:04:05 +0200 Subject: [PATCH 013/161] Test removeImportedToken --- .../services/imported-tokens.services.spec.ts | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) 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 58bd07c2918..a20422ac0ac 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -4,6 +4,7 @@ import type { ImportedToken } from "$lib/canisters/nns-dapp/nns-dapp.types"; import { addImportedToken, loadImportedTokens, + removeImportedToken, } from "$lib/services/imported-tokens.services"; import { importedTokensStore } from "$lib/stores/imported-tokens.store"; import * as toastsStore from "$lib/stores/toasts.store"; @@ -224,4 +225,97 @@ describe("imported-tokens-services", () => { }); }); }); + + describe("removeImportedToken", () => { + it("should call setImportedTokens with updated token list", async () => { + const spySetImportedTokens = vi + .spyOn(importedTokensApi, "setImportedTokens") + .mockResolvedValue(undefined); + expect(spySetImportedTokens).toBeCalledTimes(0); + + const { success } = await removeImportedToken({ + tokenToRemove: importedTokenDataA, + importedTokens: [importedTokenDataA, importedTokenDataB], + }); + + expect(success).toEqual(true); + expect(spySetImportedTokens).toBeCalledTimes(1); + expect(spySetImportedTokens).toHaveBeenCalledWith({ + identity: mockIdentity, + importedTokens: [importedTokenB], + }); + }); + + it("should update the store", async () => { + const spyGetImportedTokens = vi + .spyOn(importedTokensApi, "getImportedTokens") + .mockResolvedValue({ + imported_tokens: [importedTokenB], + }); + vi.spyOn(importedTokensApi, "setImportedTokens").mockResolvedValue( + undefined + ); + importedTokensStore.set({ + importedTokens: [importedTokenDataA, importedTokenDataB], + certified: true, + }); + expect(spyGetImportedTokens).toBeCalledTimes(0); + expect(get(importedTokensStore)).toEqual({ + importedTokens: [importedTokenDataA, importedTokenDataB], + certified: true, + }); + + await removeImportedToken({ + tokenToRemove: importedTokenDataA, + importedTokens: [importedTokenDataA, importedTokenDataB], + }); + + expect(spyGetImportedTokens).toBeCalledTimes(2); + expect(get(importedTokensStore)).toEqual({ + importedTokens: [importedTokenDataB], + certified: true, + }); + }); + + it("should display success toast", async () => { + const spyToastSuccsess = vi.spyOn(toastsStore, "toastsSuccess"); + vi.spyOn(importedTokensApi, "setImportedTokens").mockRejectedValue( + undefined + ); + vi.spyOn(importedTokensApi, "getImportedTokens").mockResolvedValue({ + imported_tokens: [importedTokenB], + }); + expect(spyToastSuccsess).not.toBeCalled(); + + await removeImportedToken({ + tokenToRemove: importedTokenDataA, + importedTokens: [importedTokenDataA, importedTokenDataB], + }); + + expect(spyToastSuccsess).toBeCalledTimes(1); + expect(spyToastSuccsess).toBeCalledWith({ + labelKey: "tokens.remove_imported_token_success", + }); + }); + + it("should display toast on error", async () => { + const spyToastError = vi.spyOn(toastsStore, "toastsError"); + vi.spyOn(importedTokensApi, "setImportedTokens").mockRejectedValue( + testError + ); + expect(spyToastError).not.toBeCalled(); + + const { success } = await removeImportedToken({ + tokenToRemove: importedTokenDataA, + importedTokens: [importedTokenDataA, importedTokenDataB], + }); + + expect(success).toEqual(false); + expect(spyToastError).toBeCalledTimes(1); + expect(spyToastError).toBeCalledWith({ + labelKey: "error__imported_tokens.remove_imported_token", + err: testError, + }); + }); + }); }); From 9af385959d10af35cb63e0a6cd004210594e5876 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 21:15:08 +0200 Subject: [PATCH 014/161] Fix typo --- frontend/src/lib/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 7691b4c8b21..1915f884607 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -849,7 +849,7 @@ "error__imported_tokens": { "load_imported_tokens": "There was an unexpected issue while loading imported tokens.", "add_imported_token": "There was an unexpected issue while adding new imported token.", - "remove_imported_token": "There was an unexpected issue while adding new imported token.", + "remove_imported_token": "There was an unexpected issue while removing the imported token.", "too_many": "You can't import more than $limit tokens." }, "error__sns": { From 2fa54de23773860b8000053fcc3a6a88fdb95020 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 28 Jul 2024 21:25:31 +0200 Subject: [PATCH 015/161] Comments --- .../src/lib/services/imported-tokens.services.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index df23ed1c4ad..4756f515c99 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -18,6 +18,9 @@ import { import { isNullish } from "@dfinity/utils"; import { queryAndUpdate } from "./utils.services"; +/** Load imported tokens from the `nns-dapp` backend and update the `importedTokensStore` store. + * - Displays an error toast if the operation fails. + */ export const loadImportedTokens = async () => { return queryAndUpdate({ request: (options) => getImportedTokens(options), @@ -46,6 +49,8 @@ export const loadImportedTokens = async () => { }); }; +// Save imported tokens to the nns-dapp backend. +// Returns an error if the operation fails. const saveImportedToken = async ({ tokens, }: { @@ -62,6 +67,11 @@ const saveImportedToken = async ({ return { err: undefined }; }; +/** + * Add new imported token and reload imported tokens from the `nns-dapp` backend to update the `importedTokensStore`. + * - Displays a success toast if the operation is successful. + * - Displays an error toast if the operation fails. + */ export const addImportedToken = async ({ tokenToAdd, importedTokens, @@ -98,6 +108,11 @@ export const addImportedToken = async ({ return { success: false }; }; +/** + * Remove an imported token and reload imported tokens from the `nns-dapp` backend to update the `importedTokensStore`. + * - Displays a success toast if the operation is successful. + * - Displays an error toast if the operation fails. + */ export const removeImportedToken = async ({ tokenToRemove, importedTokens, From 70cece1e3cfe1d0f584bc1b01c5aab55d38faab4 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 29 Jul 2024 09:39:52 +0200 Subject: [PATCH 016/161] Todo added --- frontend/src/lib/services/app.services.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/lib/services/app.services.ts b/frontend/src/lib/services/app.services.ts index e1ac72c0e6b..e8f6787b5c4 100644 --- a/frontend/src/lib/services/app.services.ts +++ b/frontend/src/lib/services/app.services.ts @@ -9,6 +9,7 @@ export const initAppPrivateData = async (): Promise => { // Get latest data and create wrapper caches for the logged in identity. const initSns: Promise[] = [loadSnsProjects()]; + // TODO: load imported tokens after Nns. /** * If Nns load but Sns load fails it is "fine" to go on because Nns are core features. */ From a3bc1da44ba608266fde803466842fd750d751ca Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 11:15:09 +0200 Subject: [PATCH 017/161] Add ImportTokenWarning --- .../accounts/ImportTokenWarning.svelte | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 frontend/src/lib/components/accounts/ImportTokenWarning.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenWarning.svelte b/frontend/src/lib/components/accounts/ImportTokenWarning.svelte new file mode 100644 index 00000000000..bc254b8f09c --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenWarning.svelte @@ -0,0 +1,31 @@ + + +
+ +

+
+ + From 0b30654448c725f2b3eb8b3a690a542cf27ae74d Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 11:15:41 +0200 Subject: [PATCH 018/161] Prop "required" for PrincipalInput --- frontend/src/lib/components/ui/PrincipalInput.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/lib/components/ui/PrincipalInput.svelte b/frontend/src/lib/components/ui/PrincipalInput.svelte index baedf068c4b..f1bcfae629c 100644 --- a/frontend/src/lib/components/ui/PrincipalInput.svelte +++ b/frontend/src/lib/components/ui/PrincipalInput.svelte @@ -7,6 +7,7 @@ export let placeholderLabelKey: string; export let name: string; export let principal: Principal | undefined = undefined; + export let required: boolean | undefined = undefined; let address = principal?.toText() ?? ""; $: principal = getPrincipalFromString(address); @@ -27,6 +28,7 @@ errorMessage={showError ? $i18n.error.principal_not_valid : undefined} on:blur={showErrorIfAny} showInfo={$$slots.label !== undefined} + {required} > From c97d4e30599cd9b452cdbf82c131c102239f4868 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 11:16:12 +0200 Subject: [PATCH 019/161] New labels # Conflicts: # frontend/src/lib/i18n/en.json --- frontend/src/lib/i18n/en.json | 15 ++++++++++++++- frontend/src/lib/pages/Tokens.svelte | 2 +- frontend/src/lib/types/i18n.d.ts | 17 ++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 1915f884607..5cc9e24dd97 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1037,8 +1037,21 @@ "hide_zero_balances_toggle_label": "Switch between showing and hiding tokens with a balance of zero", "zero_balance_hidden": "Tokens with 0 balances are hidden.", "show_all": "Show all", - "import_token": "Import Token", "add_imported_token_success": "New token has been successfully imported!", "remove_imported_token_success": "The token has been successfully removed!" + }, + "import_token": { + "import_token": "Import Token", + "description": "To import a new token to your NNS dapp wallet, you will need to find, and paste the ledger canister id of the token. If you want to see your transaction history, you need to import the token’s index canister.", + "where_to_find": "Read where to find Ledger Canister IDs", + "where_to_find_href": "https://where-to-find-the-ledger-id.com", + "ledger_label": "Ledger Canister ID", + "index_label": "Index Canister ID (Optional)", + "placeholder": "00000-00000-00000-00000-000", + "index_canister_description": "Index Canister allows to display a token balance and transaction history. Note: not all tokens have index canisters.", + "warning": "Warning: Be careful what token you import! Anyone can create a token including one with the same name as existing tokens, such as ckBTC", + "import_button": "Import", + "verifying": "Veryifying token details...", + "review_token_info": "Review token info" } } diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index 6c11737f20d..61da11c876c 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -84,7 +84,7 @@ class="ghost with-icon import-token-button" on:click={importToken} > - {$i18n.tokens.import_token} + {$i18n.import_token.import_token} {:else if shouldHideZeroBalances} diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 5908d011372..7be2caa3e2b 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1097,11 +1097,25 @@ interface I18nTokens { hide_zero_balances_toggle_label: string; zero_balance_hidden: string; show_all: string; - import_token: string; add_imported_token_success: string; remove_imported_token_success: string; } +interface I18nImport_token { + import_token: string; + description: string; + where_to_find: string; + where_to_find_href: string; + ledger_label: string; + index_label: string; + placeholder: string; + index_canister_description: string; + warning: string; + import_button: string; + verifying: string; + review_token_info: string; +} + interface I18nNeuron_state { Unspecified: string; Locked: string; @@ -1386,6 +1400,7 @@ interface I18n { settings: I18nSettings; sync: I18nSync; tokens: I18nTokens; + import_token: I18nImport_token; neuron_state: I18nNeuron_state; topics: I18nTopics; topics_description: I18nTopics_description; From 57dce25736b00b6a10db99e321bc683464dc1207 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 11:16:39 +0200 Subject: [PATCH 020/161] ImportTokenForm component base --- .../accounts/ImportTokenForm.svelte | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 frontend/src/lib/components/accounts/ImportTokenForm.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenForm.svelte b/frontend/src/lib/components/accounts/ImportTokenForm.svelte new file mode 100644 index 00000000000..a677e2ae4ea --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenForm.svelte @@ -0,0 +1,89 @@ + + + +

{$i18n.import_token.description}

+ + + + {$i18n.import_token.where_to_find} + +
+ + {$i18n.import_token.ledger_label} + + + + + + +

+ +

+ + + +
+ + + +
+ +
+ + From f8fa6c54dff9243c466aa9830b0d517125f38853 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 11:57:30 +0200 Subject: [PATCH 021/161] Dispatch nnsSubmit from ImportTokenForm --- frontend/src/lib/components/accounts/ImportTokenForm.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/accounts/ImportTokenForm.svelte b/frontend/src/lib/components/accounts/ImportTokenForm.svelte index a677e2ae4ea..ab4768907ee 100644 --- a/frontend/src/lib/components/accounts/ImportTokenForm.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenForm.svelte @@ -29,7 +29,7 @@ {$i18n.import_token.where_to_find} -
+ dispatch('nnsSubmit')}> Date: Thu, 18 Jul 2024 11:58:38 +0200 Subject: [PATCH 022/161] New ImportTokenModal component with basic validation --- frontend/src/lib/i18n/en.json | 3 +- .../modals/accounts/ImportTokenModal.svelte | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/modals/accounts/ImportTokenModal.svelte diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 5cc9e24dd97..a0dc5efd6e8 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1052,6 +1052,7 @@ "warning": "Warning: Be careful what token you import! Anyone can create a token including one with the same name as existing tokens, such as ckBTC", "import_button": "Import", "verifying": "Veryifying token details...", - "review_token_info": "Review token info" + "review_token_info": "Review token info", + "ledger_canister_loading_error": "Unable to load token details using the provided Ledger Canister ID." } } diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte new file mode 100644 index 00000000000..1cc9b774f39 --- /dev/null +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -0,0 +1,111 @@ + + + + {currentStep?.title} + + {#if currentStep?.name === STEP_FORM} + + {/if} + {#if currentStep?.name === STEP_REVIEW} + TBD: ImportTokenReview + {/if} + {#if currentStep?.name === STEP_IN_PROGRESS} + TBD: ImportTokenInProgress + {/if} + From ca474b3f23e06c13e8272bcbf9d6be290df52c2a Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 11:59:06 +0200 Subject: [PATCH 023/161] Add `import-token-validation` busy initiator type --- frontend/src/lib/stores/busy.store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/stores/busy.store.ts b/frontend/src/lib/stores/busy.store.ts index 95291a7be3b..954680c21e3 100644 --- a/frontend/src/lib/stores/busy.store.ts +++ b/frontend/src/lib/stores/busy.store.ts @@ -45,7 +45,8 @@ export type BusyStateInitiatorType = | "dev-add-sns-neuron-maturity" | "dev-add-nns-neuron-maturity" | "update-ckbtc-balance" - | "reload-receive-account"; + | "reload-receive-account" + | "import-token-validation"; export interface BusyState { initiator: BusyStateInitiatorType; From fdf850efe9b19f9dc7701dfe2bb0e77086fb5caf Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 12:01:44 +0200 Subject: [PATCH 024/161] Busy screen text --- frontend/src/lib/modals/accounts/ImportTokenModal.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index 1cc9b774f39..2487a7d8da6 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -73,7 +73,8 @@ const onUserInput = async () => { // TODO: check the uniqueness of the ledgerCanisterId - startBusy({ initiator: "import-token-validation" }); + // TODO: test the busy screen text + startBusy({ initiator: "import-token-validation", labelKey: 'import_token.verifying' }); await updateTokenMetaData(); stopBusy("import-token-validation"); From aa4e1434ccc7605deda313fedb82ed14138187e8 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 13:35:19 +0200 Subject: [PATCH 025/161] Add LinkIcon component --- .../src/lib/components/common/LinkIcon.svelte | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 frontend/src/lib/components/common/LinkIcon.svelte diff --git a/frontend/src/lib/components/common/LinkIcon.svelte b/frontend/src/lib/components/common/LinkIcon.svelte new file mode 100644 index 00000000000..cebe0267159 --- /dev/null +++ b/frontend/src/lib/components/common/LinkIcon.svelte @@ -0,0 +1,26 @@ + + + + + From 594c776da0e35016858ab09d6b012e9bfb77ba9f Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 13:42:02 +0200 Subject: [PATCH 026/161] Add ImportTokenCanisterId component --- .../accounts/ImportTokenCanisterId.svelte | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte b/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte new file mode 100644 index 00000000000..3589d0431e7 --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte @@ -0,0 +1,37 @@ + + +
+ {label} +
+ {canisterId} + {#if nonNullish(canisterId) && nonNullish(canisterLinkHref)} + + + {:else if nonNullish(canisterIdFallback)} + {canisterIdFallback} + {/if} +
+
+ + From f1064eaec06d6d95df0db989e2ca579fe1a9755c Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 13:46:38 +0200 Subject: [PATCH 027/161] Add ImportTokenReview component --- .../accounts/ImportTokenReview.svelte | 99 +++++++++++++++++++ frontend/src/lib/i18n/en.json | 6 +- frontend/src/lib/types/i18n.d.ts | 5 + 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/components/accounts/ImportTokenReview.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenReview.svelte b/frontend/src/lib/components/accounts/ImportTokenReview.svelte new file mode 100644 index 00000000000..6b7baee2815 --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenReview.svelte @@ -0,0 +1,99 @@ + + +
+
+ +
+
{tokenMetaData.name}
+
{tokenMetaData.symbol}
+
+
+ + + + + + + + + + + +
+ + + +
+
+ + diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index a0dc5efd6e8..652d8550fc6 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1046,13 +1046,17 @@ "where_to_find": "Read where to find Ledger Canister IDs", "where_to_find_href": "https://where-to-find-the-ledger-id.com", "ledger_label": "Ledger Canister ID", - "index_label": "Index Canister ID (Optional)", + "index_label_optional": "Index Canister ID (Optional)", + "index_label": "Index Canister ID", + "index_fallback_label": "Transaction history won’t be displayed.", "placeholder": "00000-00000-00000-00000-000", "index_canister_description": "Index Canister allows to display a token balance and transaction history. Note: not all tokens have index canisters.", "warning": "Warning: Be careful what token you import! Anyone can create a token including one with the same name as existing tokens, such as ckBTC", "import_button": "Import", "verifying": "Veryifying token details...", "review_token_info": "Review token info", + "link_to_ledger_canister": "https://where-to-find-the-ledger-id.com/$canisterId", + "link_to_index_canister": "https://where-to-find-the-ledger-id.com/$canisterId", "ledger_canister_loading_error": "Unable to load token details using the provided Ledger Canister ID." } } diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 7be2caa3e2b..acb2023ac14 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1107,13 +1107,18 @@ interface I18nImport_token { where_to_find: string; where_to_find_href: string; ledger_label: string; + index_label_optional: string; index_label: string; + index_fallback_label: string; placeholder: string; index_canister_description: string; warning: string; import_button: string; verifying: string; review_token_info: string; + link_to_ledger_canister: string; + link_to_index_canister: string; + ledger_canister_loading_error: string; } interface I18nNeuron_state { From 58941d811ffd6b3909a8a08a040cecd1b4bafda3 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 13:47:23 +0200 Subject: [PATCH 028/161] Change index_label_optional label --- .../src/lib/components/accounts/ImportTokenForm.svelte | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/components/accounts/ImportTokenForm.svelte b/frontend/src/lib/components/accounts/ImportTokenForm.svelte index ab4768907ee..ee69b733b89 100644 --- a/frontend/src/lib/components/accounts/ImportTokenForm.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenForm.svelte @@ -2,7 +2,7 @@ import type { Principal } from "@dfinity/principal"; import PrincipalInput from "$lib/components/ui/PrincipalInput.svelte"; import { i18n } from "$lib/stores/i18n"; - import { Html, IconInfo, IconOpenInNew } from "@dfinity/gix-components"; + import { Html, IconOpenInNew } from "@dfinity/gix-components"; import TestIdWrapper from "$lib/components/common/TestIdWrapper.svelte"; import ImportTokenWarning from "$lib/components/accounts/ImportTokenWarning.svelte"; import { createEventDispatcher } from "svelte"; @@ -24,12 +24,13 @@ class="where-to-find" href={$i18n.import_token.where_to_find_href} target="_blank" + rel="noopener noreferrer" > - + {$i18n.import_token.where_to_find} - dispatch('nnsSubmit')}> + dispatch("nnsSubmit")}> - +

From 0cab007e796b2a6cd65db5102e36d60bb15dccbf Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 13:48:03 +0200 Subject: [PATCH 029/161] Display ImportTokenReview in modal --- .../modals/accounts/ImportTokenModal.svelte | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index 2487a7d8da6..d8707894470 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -19,6 +19,7 @@ import { startBusy, stopBusy } from "$lib/stores/busy.store"; import { isNullish, nonNullish } from "@dfinity/utils"; import { toastsError } from "$lib/stores/toasts.store"; + import ImportTokenReview from "$lib/components/accounts/ImportTokenReview.svelte"; export let currentStep: WizardStep | undefined = undefined; @@ -73,16 +74,28 @@ const onUserInput = async () => { // TODO: check the uniqueness of the ledgerCanisterId - // TODO: test the busy screen text - startBusy({ initiator: "import-token-validation", labelKey: 'import_token.verifying' }); + // TEST: should display the busy screen + // TEST: should display the busy screen text + startBusy({ + initiator: "import-token-validation", + labelKey: "import_token.verifying", + }); await updateTokenMetaData(); stopBusy("import-token-validation"); - console.log("tokenMetaData", tokenMetaData, modal); if (nonNullish(tokenMetaData)) { next(); } }; + + const onUserConfirm = async () => { + console.log( + "onUserConfirm", + ledgerCanisterId, + indexCanisterId, + tokenMetaData + ); + }; {/if} - {#if currentStep?.name === STEP_REVIEW} - TBD: ImportTokenReview + {#if currentStep?.name === STEP_REVIEW && nonNullish(ledgerCanisterId) && nonNullish(tokenMetaData)} + {/if} {#if currentStep?.name === STEP_IN_PROGRESS} TBD: ImportTokenInProgress From 0d857d24c87e8343cc1ba01cfa88c96c5f1beb12 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 18 Jul 2024 13:48:35 +0200 Subject: [PATCH 030/161] Show modal on import token click --- frontend/src/lib/pages/Tokens.svelte | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index 61da11c876c..63fa57ad89e 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -11,6 +11,7 @@ import { Popover } from "@dfinity/gix-components"; import { TokenAmountV2 } from "@dfinity/utils"; import { ENABLE_IMPORT_TOKEN } from "$lib/stores/feature-flags.store"; + import ImportTokenModal from "$lib/modals/accounts/ImportTokenModal.svelte"; export let userTokensData: UserToken[]; @@ -41,9 +42,7 @@ hideZeroBalancesStore.set("show"); }; - const importToken = async () => { - // TBD: Implement import token. - }; + let showImportTokenModal = false; // TODO(Import token): After removing ENABLE_IMPORT_TOKEN combine divs ->

@@ -82,7 +81,7 @@ @@ -112,6 +111,10 @@ > + + {#if showImportTokenModal} + showImportTokenModal = false} /> + {/if} diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index a16908724ef..160c62aa710 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1042,7 +1042,6 @@ "import_token": "Import Token", "description": "To import a new token to your NNS dapp wallet, you will need to find, and paste the ledger canister id of the token. If you want to see your transaction history, you need to import the token’s index canister.", "where_to_find": "Read where to find Ledger Canister IDs", - "where_to_find_href": "https://where-to-find-the-ledger-id.com", "ledger_label": "Ledger Canister ID", "index_label_optional": "Index Canister ID (Optional)", "index_label": "Index Canister ID", diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index abea7298a15..7a9cbf60fd7 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1103,7 +1103,6 @@ interface I18nImport_token { import_token: string; description: string; where_to_find: string; - where_to_find_href: string; ledger_label: string; index_label_optional: string; index_label: string; From 29415bfa863a582a3c90e91c13aadddb3e84d181 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 29 Jul 2024 16:55:59 +0200 Subject: [PATCH 041/161] Comments --- frontend/src/lib/modals/accounts/ImportTokenModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index c4829f1bacb..daa17c10faa 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -62,13 +62,13 @@ }; const onUserInput = async () => { - // TEST: should display the busy screen - // TEST: should display the busy screen text startBusy({ initiator: "import-token-validation", labelKey: "import_token.verifying", }); await updateTokenMetaData(); + // TODO: validate index canister id here (if provided) + stopBusy("import-token-validation"); if (nonNullish(tokenMetaData)) { From eff83c36b19725814dfffb0d27d88b76dc5e44a6 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 29 Jul 2024 17:03:53 +0200 Subject: [PATCH 042/161] Remove unused labels --- frontend/src/lib/i18n/en.json | 1 - frontend/src/lib/types/i18n.d.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 160c62aa710..dd07ec2e8cc 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1041,7 +1041,6 @@ "import_token": { "import_token": "Import Token", "description": "To import a new token to your NNS dapp wallet, you will need to find, and paste the ledger canister id of the token. If you want to see your transaction history, you need to import the token’s index canister.", - "where_to_find": "Read where to find Ledger Canister IDs", "ledger_label": "Ledger Canister ID", "index_label_optional": "Index Canister ID (Optional)", "index_label": "Index Canister ID", diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 7a9cbf60fd7..3e6be41f647 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1102,7 +1102,6 @@ interface I18nTokens { interface I18nImport_token { import_token: string; description: string; - where_to_find: string; ledger_label: string; index_label_optional: string; index_label: string; From be035e91a3b18789d8c6a331b1f72eb92898bb7b Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 29 Jul 2024 17:04:04 +0200 Subject: [PATCH 043/161] refactro: rename fetchIcrcTokenMetaData --- frontend/src/lib/modals/accounts/ImportTokenModal.svelte | 6 +++--- frontend/src/lib/services/icrc-accounts.services.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index daa17c10faa..b537120234d 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -7,7 +7,7 @@ import { i18n } from "$lib/stores/i18n"; import type { Principal } from "@dfinity/principal"; import ImportTokenForm from "$lib/components/accounts/ImportTokenForm.svelte"; - import { fetchIcrcToken } from "$lib/services/icrc-accounts.services"; + import { fetchIcrcTokenMetaData } from "$lib/services/icrc-accounts.services"; import type { IcrcTokenMetadata } from "$lib/types/icrc"; import { startBusy, stopBusy } from "$lib/stores/busy.store"; import { isNullish, nonNullish } from "@dfinity/utils"; @@ -48,9 +48,9 @@ if (isNullish(ledgerCanisterId)) { return; } - const meta = await fetchIcrcToken({ ledgerCanisterId }); + const meta = await fetchIcrcTokenMetaData({ ledgerCanisterId }); - if (meta === null) { + if (isNullish(meta)) { tokenMetaData = undefined; toastsError({ labelKey: "import_token.ledger_canister_loading_error", diff --git a/frontend/src/lib/services/icrc-accounts.services.ts b/frontend/src/lib/services/icrc-accounts.services.ts index 86e6fa81f60..bcb3ae81d0f 100644 --- a/frontend/src/lib/services/icrc-accounts.services.ts +++ b/frontend/src/lib/services/icrc-accounts.services.ts @@ -36,7 +36,7 @@ export const getIcrcAccountIdentity = (_: Account): Promise => { }; // Returns null if the token is not found -export const fetchIcrcToken = async ({ +export const fetchIcrcTokenMetaData = async ({ ledgerCanisterId, }: { ledgerCanisterId: Principal; From e306495e9dea941f9aed19fdf834b40ead8d38ca Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 29 Jul 2024 17:07:13 +0200 Subject: [PATCH 044/161] cleanup: imports --- frontend/src/lib/components/accounts/ImportTokenWarning.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/lib/components/accounts/ImportTokenWarning.svelte b/frontend/src/lib/components/accounts/ImportTokenWarning.svelte index bc254b8f09c..3b5bbac772b 100644 --- a/frontend/src/lib/components/accounts/ImportTokenWarning.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenWarning.svelte @@ -1,5 +1,4 @@ From 2146d13cd845a30c53692b40cdcd6de78b65fb10 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 30 Jul 2024 12:07:13 +0200 Subject: [PATCH 045/161] Add more testIds --- .../accounts/ImportTokenCanisterId.svelte | 5 +-- .../accounts/ImportTokenForm.svelte | 4 ++- .../accounts/ImportTokenReview.svelte | 36 +++++++++---------- .../accounts/ImportTokenWarning.svelte | 1 + .../src/lib/components/common/LinkIcon.svelte | 2 +- .../lib/components/ui/PrincipalInput.svelte | 2 ++ .../modals/accounts/ImportTokenModal.svelte | 5 ++- 7 files changed, 32 insertions(+), 23 deletions(-) diff --git a/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte b/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte index 3589d0431e7..cd0c15f3c1b 100644 --- a/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenCanisterId.svelte @@ -4,13 +4,14 @@ import LinkIcon from "$lib/components/common/LinkIcon.svelte"; export let label: string; + export let testId: string; export let canisterId: string | undefined = undefined; export let canisterLinkHref: string | undefined = undefined; export let canisterIdFallback: string | undefined = undefined; -
- {label} +
+ {label}
{canisterId} {#if nonNullish(canisterId) && nonNullish(canisterLinkHref)} diff --git a/frontend/src/lib/components/accounts/ImportTokenForm.svelte b/frontend/src/lib/components/accounts/ImportTokenForm.svelte index 57f5322b324..ee60c2bbb67 100644 --- a/frontend/src/lib/components/accounts/ImportTokenForm.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenForm.svelte @@ -25,6 +25,7 @@ bind:principal={ledgerCanisterId} placeholderLabelKey="import_token.placeholder" name="ledger-canister-id" + testId="ledger-canister-id" > {$i18n.import_token.ledger_label} diff --git a/frontend/src/lib/components/accounts/ImportTokenReview.svelte b/frontend/src/lib/components/accounts/ImportTokenReview.svelte index 5cdcb930a65..b0fd26ee32c 100644 --- a/frontend/src/lib/components/accounts/ImportTokenReview.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenReview.svelte @@ -1,7 +1,6 @@
diff --git a/frontend/src/lib/components/common/LinkIcon.svelte b/frontend/src/lib/components/common/LinkIcon.svelte index cebe0267159..caf2293e603 100644 --- a/frontend/src/lib/components/common/LinkIcon.svelte +++ b/frontend/src/lib/components/common/LinkIcon.svelte @@ -1,5 +1,5 @@ diff --git a/frontend/src/lib/components/ui/PrincipalInput.svelte b/frontend/src/lib/components/ui/PrincipalInput.svelte index f1bcfae629c..9c224c1ddd6 100644 --- a/frontend/src/lib/components/ui/PrincipalInput.svelte +++ b/frontend/src/lib/components/ui/PrincipalInput.svelte @@ -8,6 +8,7 @@ export let name: string; export let principal: Principal | undefined = undefined; export let required: boolean | undefined = undefined; + export let testId: string | undefined = undefined; let address = principal?.toText() ?? ""; $: principal = getPrincipalFromString(address); @@ -24,6 +25,7 @@ inputType="text" {placeholderLabelKey} {name} + {testId} bind:value={address} errorMessage={showError ? $i18n.error.principal_not_valid : undefined} on:blur={showErrorIfAny} diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index b537120234d..fef0afd2815 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -77,7 +77,10 @@ }; const onUserConfirm = async () => { - if (isNullish(ledgerCanisterId) || isNullish($importedTokensStore.importedTokens)) { + if ( + isNullish(ledgerCanisterId) || + isNullish($importedTokensStore.importedTokens) + ) { return; } From 460bd3e5e7c988cc52b451b3011c239b65c4e513 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:16:23 +0200 Subject: [PATCH 046/161] feat: propagate more props from PrincipalInput --- frontend/src/lib/components/ui/PrincipalInput.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/lib/components/ui/PrincipalInput.svelte b/frontend/src/lib/components/ui/PrincipalInput.svelte index baedf068c4b..9c224c1ddd6 100644 --- a/frontend/src/lib/components/ui/PrincipalInput.svelte +++ b/frontend/src/lib/components/ui/PrincipalInput.svelte @@ -7,6 +7,8 @@ export let placeholderLabelKey: string; export let name: string; export let principal: Principal | undefined = undefined; + export let required: boolean | undefined = undefined; + export let testId: string | undefined = undefined; let address = principal?.toText() ?? ""; $: principal = getPrincipalFromString(address); @@ -23,10 +25,12 @@ inputType="text" {placeholderLabelKey} {name} + {testId} bind:value={address} errorMessage={showError ? $i18n.error.principal_not_valid : undefined} on:blur={showErrorIfAny} showInfo={$$slots.label !== undefined} + {required} > From 09ccd99d2d2adccb341f45893a910d636b3bd5b7 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:16:44 +0200 Subject: [PATCH 047/161] New component ImportTokenForm --- .../accounts/ImportTokenForm.svelte | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 frontend/src/lib/components/accounts/ImportTokenForm.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenForm.svelte b/frontend/src/lib/components/accounts/ImportTokenForm.svelte new file mode 100644 index 00000000000..764507daed0 --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenForm.svelte @@ -0,0 +1,72 @@ + + + +

{$i18n.import_token.description}

+ + dispatch("nnsSubmit")}> + + {$i18n.import_token.ledger_label} + + + + + + +

+ +

+ + + +
+ + + +
+ +
+ + From f793680fd5808f47ad27e9f1126ca813473ea4f4 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:23:03 +0200 Subject: [PATCH 048/161] Update labels --- frontend/src/lib/i18n/en.json | 16 +++++++++++++++- frontend/src/lib/types/i18n.d.ts | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 1915f884607..a0097e61326 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1036,8 +1036,22 @@ "hide_zero_balances": "Hide zero balances", "hide_zero_balances_toggle_label": "Switch between showing and hiding tokens with a balance of zero", "zero_balance_hidden": "Tokens with 0 balances are hidden.", - "show_all": "Show all", + "show_all": "Show all" + }, + "import_token": { "import_token": "Import Token", + "description": "To import a new token to your NNS dapp wallet, you will need to find, and paste the ledger canister id of the token. If you want to see your transaction history, you need to import the token’s index canister.", + "ledger_label": "Ledger Canister ID", + "index_label_optional": "Index Canister ID (Optional)", + "index_label": "Index Canister ID", + "index_fallback_label": "Transaction history won’t be displayed.", + "placeholder": "00000-00000-00000-00000-000", + "index_canister_description": "Index Canister allows to display a token balance and transaction history. Note: not all tokens have index canisters.", + "warning": "Warning: Be careful what token you import! Anyone can create a token including one with the same name as existing tokens, such as ckBTC", + "verifying": "Veryifying token details...", + "review_token_info": "Review token info", + "import_button": "Import", + "link_to_canister": "https://dashboard.internetcomputer.org/canister/$canisterId", "add_imported_token_success": "New token has been successfully imported!", "remove_imported_token_success": "The token has been successfully removed!" } diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 5908d011372..cc9a0ef02fb 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1097,7 +1097,22 @@ interface I18nTokens { hide_zero_balances_toggle_label: string; zero_balance_hidden: string; show_all: string; +} + +interface I18nImport_token { import_token: string; + description: string; + ledger_label: string; + index_label_optional: string; + index_label: string; + index_fallback_label: string; + placeholder: string; + index_canister_description: string; + warning: string; + verifying: string; + review_token_info: string; + import_button: string; + link_to_canister: string; add_imported_token_success: string; remove_imported_token_success: string; } @@ -1386,6 +1401,7 @@ interface I18n { settings: I18nSettings; sync: I18nSync; tokens: I18nTokens; + import_token: I18nImport_token; neuron_state: I18nNeuron_state; topics: I18nTopics; topics_description: I18nTopics_description; From 5e3f2eb3cdc6e40c49fc61a4849f85689908251a Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:23:53 +0200 Subject: [PATCH 049/161] New busy store initiator --- frontend/src/lib/stores/busy.store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/stores/busy.store.ts b/frontend/src/lib/stores/busy.store.ts index 95291a7be3b..954680c21e3 100644 --- a/frontend/src/lib/stores/busy.store.ts +++ b/frontend/src/lib/stores/busy.store.ts @@ -45,7 +45,8 @@ export type BusyStateInitiatorType = | "dev-add-sns-neuron-maturity" | "dev-add-nns-neuron-maturity" | "update-ckbtc-balance" - | "reload-receive-account"; + | "reload-receive-account" + | "import-token-validation"; export interface BusyState { initiator: BusyStateInitiatorType; From 77d3b2084878704bc701a153733ac753266e3882 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:24:02 +0200 Subject: [PATCH 050/161] feat: fetchIcrcTokenMetaData service --- .../src/lib/services/icrc-accounts.services.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/services/icrc-accounts.services.ts b/frontend/src/lib/services/icrc-accounts.services.ts index 32fd445e16a..bcb3ae81d0f 100644 --- a/frontend/src/lib/services/icrc-accounts.services.ts +++ b/frontend/src/lib/services/icrc-accounts.services.ts @@ -5,7 +5,10 @@ import { } from "$lib/api/icrc-ledger.api"; import { FORCE_CALL_STRATEGY } from "$lib/constants/mockable.constants"; import { snsTokensByLedgerCanisterIdStore } from "$lib/derived/sns/sns-tokens.derived"; -import { getAuthenticatedIdentity } from "$lib/services/auth.services"; +import { + getAuthenticatedIdentity, + getCurrentIdentity, +} from "$lib/services/auth.services"; import { icrcAccountsStore } from "$lib/stores/icrc-accounts.store"; import { icrcTransactionsStore } from "$lib/stores/icrc-transactions.store"; import { toastsError } from "$lib/stores/toasts.store"; @@ -32,6 +35,19 @@ export const getIcrcAccountIdentity = (_: Account): Promise => { return getAuthenticatedIdentity(); }; +// Returns null if the token is not found +export const fetchIcrcTokenMetaData = async ({ + ledgerCanisterId, +}: { + ledgerCanisterId: Principal; +}): Promise => { + return queryIcrcToken({ + identity: getCurrentIdentity(), + canisterId: ledgerCanisterId, + certified: false, + }).catch(() => null); +}; + export const loadIcrcToken = ({ ledgerCanisterId, certified = true, From 73e8970b243f12a7ead626df3bfab72feaa9eb00 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:24:36 +0200 Subject: [PATCH 051/161] New component ImportTokenReview --- .../accounts/ImportTokenReview.svelte | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 frontend/src/lib/components/accounts/ImportTokenReview.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenReview.svelte b/frontend/src/lib/components/accounts/ImportTokenReview.svelte new file mode 100644 index 00000000000..3b179b3a436 --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenReview.svelte @@ -0,0 +1,108 @@ + + +
+
+ +
+
{tokenMetaData.name}
+
+ {tokenMetaData.symbol} +
+
+
+ + + + + + + + + + + +
+ + + +
+
+ + From e988727368c2f44c036c6b6a765efd4bf0918b84 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:25:07 +0200 Subject: [PATCH 052/161] New component ImportTokenModal --- .../modals/accounts/ImportTokenModal.svelte | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 frontend/src/lib/modals/accounts/ImportTokenModal.svelte diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte new file mode 100644 index 00000000000..de3dc051f42 --- /dev/null +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -0,0 +1,106 @@ + + + + {currentStep?.title} + + {#if currentStep?.name === STEP_FORM} + + {/if} + {#if currentStep?.name === STEP_REVIEW && nonNullish(ledgerCanisterId) && nonNullish(tokenMetaData)} + + {/if} + From ffecb60c36c80060df80f828a660eb2e785cbca6 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 31 Jul 2024 11:25:36 +0200 Subject: [PATCH 053/161] feat: display the modal on import token click --- frontend/src/lib/pages/Tokens.svelte | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index 6c11737f20d..92e3c231ca5 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -11,6 +11,7 @@ import { Popover } from "@dfinity/gix-components"; import { TokenAmountV2 } from "@dfinity/utils"; import { ENABLE_IMPORT_TOKEN } from "$lib/stores/feature-flags.store"; + import ImportTokenModal from "$lib/modals/accounts/ImportTokenModal.svelte"; export let userTokensData: UserToken[]; @@ -41,9 +42,7 @@ hideZeroBalancesStore.set("show"); }; - const importToken = async () => { - // TBD: Implement import token. - }; + let showImportTokenModal = false; // TODO(Import token): After removing ENABLE_IMPORT_TOKEN combine divs ->
@@ -82,9 +81,9 @@
{:else if shouldHideZeroBalances} @@ -112,6 +111,10 @@ > + + {#if showImportTokenModal} + (showImportTokenModal = false)} /> + {/if} diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 38c330178d3..84e839b68ef 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1051,6 +1051,7 @@ "warning": "Warning: Be careful what token you import! Anyone can create a token including one with the same name as existing tokens, such as ckBTC.", "verifying": "Veryifying token details...", "importing": "Importing new token...", + "removing": "Removing imported token...", "review_token_info": "Review token info", "import_button": "Import", "ledger_canister_loading_error": "Unable to load token details using the provided Ledger Canister ID.", diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index b5f61ee0e51..0f05de23f4d 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -135,9 +135,12 @@ export const removeImportedTokens = async ({ tokensToRemove: ImportedTokenData[]; importedTokens: ImportedTokenData[]; }): Promise<{ success: boolean }> => { + const canisterIdsToRemove = tokensToRemove.map( + ({ ledgerCanisterId }) => ledgerCanisterId + ); // Compare imported tokens by their ledgerCanisterId because they should be unique. const ledgerIdsToRemove = new Set( - tokensToRemove.map(({ ledgerCanisterId }) => ledgerCanisterId.toText()) + canisterIdsToRemove.map((id) => id.toText()) ); const tokens = importedTokens.filter( ({ ledgerCanisterId }) => !ledgerIdsToRemove.has(ledgerCanisterId.toText()) @@ -145,6 +148,9 @@ export const removeImportedTokens = async ({ const { err } = await saveImportedToken({ tokens }); if (isNullish(err)) { + // TODO: update unit test to check if icrcCanistersStore.removeCanisters is called + icrcCanistersStore.removeCanisters(canisterIdsToRemove); + await loadImportedTokens(); toastsSuccess({ labelKey: "import_token.remove_imported_token_success", diff --git a/frontend/src/lib/stores/busy.store.ts b/frontend/src/lib/stores/busy.store.ts index 4edec0f7805..986f40dff4a 100644 --- a/frontend/src/lib/stores/busy.store.ts +++ b/frontend/src/lib/stores/busy.store.ts @@ -47,7 +47,8 @@ export type BusyStateInitiatorType = | "update-ckbtc-balance" | "reload-receive-account" | "import-token-validation" - | "import-token-importing"; + | "import-token-importing" + | "import-token-removing"; export interface BusyState { initiator: BusyStateInitiatorType; diff --git a/frontend/src/lib/stores/icrc-canisters.store.ts b/frontend/src/lib/stores/icrc-canisters.store.ts index 85201addc8c..c8e21b2317b 100644 --- a/frontend/src/lib/stores/icrc-canisters.store.ts +++ b/frontend/src/lib/stores/icrc-canisters.store.ts @@ -1,5 +1,6 @@ import { browser } from "$app/environment"; import type { UniverseCanisterIdText } from "$lib/types/universe"; +import { removeKeys } from "$lib/utils/utils"; import { Principal } from "@dfinity/principal"; import { nonNullish } from "@dfinity/utils"; import type { Readable } from "svelte/store"; @@ -17,6 +18,7 @@ export type IcrcCanistersStoreData = Record< export interface IcrcCanistersStore extends Readable { setCanisters: (data: IcrcCanisters) => void; + removeCanisters: (ledgerCanisterIds: Principal[]) => void; reset: () => void; } @@ -48,6 +50,17 @@ const initIcrcCanistersStore = (): IcrcCanistersStore => { })); }, + // TODO: unit test me! + // Remove entries by ledger canister id. + removeCanisters(ledgerCanisterIds: Principal[]) { + update((state: IcrcCanistersStoreData) => + removeKeys({ + obj: state, + keysToRemove: ledgerCanisterIds.map((id) => id.toText()), + }) + ); + }, + // Used in tests reset() { set(initialIcrcCanistersStoreData); From 1f576738e213106b1bcf87c24bfa7581602f6e0c Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 16:23:08 +0200 Subject: [PATCH 085/161] chore: formatting --- .../components/accounts/IcrcWalletPage.svelte | 21 ++++++++++--------- .../modals/accounts/ImportTokenModal.svelte | 18 +++++++++------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte index 9cf1615e02c..e527d6e0073 100644 --- a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte +++ b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte @@ -24,10 +24,10 @@ import type { Principal } from "@dfinity/principal"; import { TokenAmountV2, isNullish, nonNullish } from "@dfinity/utils"; import type { Writable } from "svelte/store"; - import {accountsPathStore} from "$lib/derived/paths.derived"; - import {startBusy, stopBusy} from "$lib/stores/busy.store"; - import {importedTokensStore} from "$lib/stores/imported-tokens.store"; - import {removeImportedTokens} from "$lib/services/imported-tokens.services"; + import { accountsPathStore } from "$lib/derived/paths.derived"; + import { startBusy, stopBusy } from "$lib/stores/busy.store"; + import { importedTokensStore } from "$lib/stores/imported-tokens.store"; + import { removeImportedTokens } from "$lib/services/imported-tokens.services"; export let testId: string; export let accountIdentifier: string | undefined | null = undefined; @@ -147,7 +147,9 @@ isSignedIn: $authSignedInStore, }))(); - const remove = async ({ detail }: CustomEvent<{ledgerCanisterId: Principal}>) => { + const remove = async ({ + detail, + }: CustomEvent<{ ledgerCanisterId: Principal }>) => { console.log("removeImportedToken", detail.ledgerCanisterId); startBusy({ @@ -156,9 +158,10 @@ }); const importedTokens = $importedTokensStore.importedTokens ?? []; - const {success} = await removeImportedTokens({ + const { success } = await removeImportedTokens({ tokensToRemove: importedTokens.filter( - ({ledgerCanisterId: id}) => id.toText() === detail.ledgerCanisterId.toText() + ({ ledgerCanisterId: id }) => + id.toText() === detail.ledgerCanisterId.toText() ), importedTokens, }); @@ -168,9 +171,7 @@ if (success) { goto($accountsPathStore); } - - } - + }; diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index 8f5aab0a87c..a0f544baa63 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -13,11 +13,11 @@ import { startBusy, stopBusy } from "$lib/stores/busy.store"; import { fetchIcrcTokenMetaData } from "$lib/services/icrc-accounts.services"; import { toastsError } from "$lib/stores/toasts.store"; - import {importedTokensStore} from "$lib/stores/imported-tokens.store"; - import {addImportedToken} from "$lib/services/imported-tokens.services"; - import {buildWalletUrl} from "$lib/utils/navigation.utils"; - import {goto} from "$app/navigation"; - import {createEventDispatcher} from "svelte"; + import { importedTokensStore } from "$lib/stores/imported-tokens.store"; + import { addImportedToken } from "$lib/services/imported-tokens.services"; + import { buildWalletUrl } from "$lib/utils/navigation.utils"; + import { goto } from "$app/navigation"; + import { createEventDispatcher } from "svelte"; let currentStep: WizardStep | undefined = undefined; const dispatch = createEventDispatcher(); @@ -104,9 +104,11 @@ dispatch("nnsClose"); - goto(buildWalletUrl({ - universe: ledgerCanisterId.toText(), - })); + goto( + buildWalletUrl({ + universe: ledgerCanisterId.toText(), + }) + ); stopBusy("import-token-importing"); }; From 9d6ca11b76c5c09fe52106244354d9276ac36df8 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 16:51:37 +0200 Subject: [PATCH 086/161] fix: update i18n types --- frontend/src/lib/types/i18n.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index d250517353d..e156b8cbc13 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1112,6 +1112,7 @@ interface I18nImport_token { warning: string; verifying: string; importing: string; + removing: string; review_token_info: string; import_button: string; ledger_canister_loading_error: string; From 4654cc055acbbee0052f2afc311ed1033ae64ce8 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 16:51:57 +0200 Subject: [PATCH 087/161] test: fix compareTokensByImportance arguments --- frontend/src/tests/lib/utils/tokens-table.utils.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts index ea02a064f85..f0b8b821ce6 100644 --- a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts +++ b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts @@ -100,17 +100,17 @@ describe("tokens-table.utils", () => { const expectedOrder = [ckBTCToken, ckETHTToken, ckUSDCToken, token0]; expect( [token0, ckUSDCToken, ckETHTToken, ckBTCToken].sort( - compareTokensByImportance + compareTokensByImportance({ importedTokenIds: new Set() }) ) ).toEqual(expectedOrder); expect( [ckBTCToken, ckETHTToken, ckUSDCToken, token0].sort( - compareTokensByImportance + compareTokensByImportance({ importedTokenIds: new Set() }) ) ).toEqual(expectedOrder); expect( [ckETHTToken, ckBTCToken, token0, ckUSDCToken].sort( - compareTokensByImportance + compareTokensByImportance({ importedTokenIds: new Set() }) ) ).toEqual(expectedOrder); }); From 234b0d48b474d27de5d3a1a107560ae77d6b292a Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 17:07:36 +0200 Subject: [PATCH 088/161] test: adjust "should not show errors if loading accounts fails" test --- frontend/src/tests/lib/services/app.services.spec.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/tests/lib/services/app.services.spec.ts b/frontend/src/tests/lib/services/app.services.spec.ts index 970e5913594..e41c6d0caaa 100644 --- a/frontend/src/tests/lib/services/app.services.spec.ts +++ b/frontend/src/tests/lib/services/app.services.spec.ts @@ -83,7 +83,16 @@ describe("app-services", () => { await expect(mockLedgerCanister.accountBalance).not.toBeCalled(); const toastData = get(toastsStore); - expect(toastData).toHaveLength(0); + + // The imported tokens error should be shown. + // TODO: check if this is the correct behavior + expect(toastData).toHaveLength(1); + expect(toastData).toEqual([ + expect.objectContaining({ + text: "There was an unexpected issue while loading imported tokens. Cannot read properties of undefined (reading 'imported_tokens')", + level: "error", + }), + ]); }); it("should call loadActionableProposals after Sns data is ready", async () => { From 03a0b6989ca110af04f6ef71ee07a9b84acc760b Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 18:05:50 +0200 Subject: [PATCH 089/161] chore: remove log --- frontend/src/lib/services/imported-tokens.services.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index 0f05de23f4d..37317ec3a23 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -29,7 +29,6 @@ export const loadImportedTokens = async () => { strategy: FORCE_CALL_STRATEGY, onLoad: ({ response: { imported_tokens: rawImportTokens }, certified }) => { const importedTokens = rawImportTokens.map(toImportedTokenData); - console.log("User Imported Tokens:", importedTokens); importedTokensStore.set({ importedTokens, certified, From 0179f680782485934d98e3896c17a12b1b25f312 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 18:24:08 +0200 Subject: [PATCH 090/161] ! ENABLE ENABLE_IMPORT_TOKEN FLAG ! For testing on beta --- dfx.json | 2 +- scripts/nns-dapp/test-config-assets/app/arg.did | 2 +- scripts/nns-dapp/test-config-assets/app/env | 2 +- scripts/nns-dapp/test-config-assets/mainnet/arg.did | 2 +- scripts/nns-dapp/test-config-assets/mainnet/env | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dfx.json b/dfx.json index db88e09e49e..8a5dd1e08b3 100644 --- a/dfx.json +++ b/dfx.json @@ -372,7 +372,7 @@ "ENABLE_CKBTC": true, "ENABLE_CKTESTBTC": false, "ENABLE_PROJECTS_TABLE": true, - "ENABLE_IMPORT_TOKEN": false + "ENABLE_IMPORT_TOKEN": true } } }, diff --git a/scripts/nns-dapp/test-config-assets/app/arg.did b/scripts/nns-dapp/test-config-assets/app/arg.did index 5a2452b00a3..5f28822458e 100644 --- a/scripts/nns-dapp/test-config-assets/app/arg.did +++ b/scripts/nns-dapp/test-config-assets/app/arg.did @@ -10,7 +10,7 @@ record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" }; record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" }; record{ 0="DFX_NETWORK"; 1="app" }; - record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" }; + record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" }; record{ 0="FETCH_ROOT_KEY"; 1="false" }; record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" }; record{ 0="HOST"; 1="https://icp-api.io" }; diff --git a/scripts/nns-dapp/test-config-assets/app/env b/scripts/nns-dapp/test-config-assets/app/env index 9ac6d3f20f4..218bffd98bd 100644 --- a/scripts/nns-dapp/test-config-assets/app/env +++ b/scripts/nns-dapp/test-config-assets/app/env @@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai VITE_OWN_CANISTER_ID=xnjld-hqaaa-aaaal-qb56q-cai VITE_FETCH_ROOT_KEY=false -VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" +VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" VITE_HOST=https://icp-api.io VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/ VITE_AGGREGATOR_CANISTER_URL=https://otgyv-wyaaa-aaaak-qcgba-cai.icp0.io diff --git a/scripts/nns-dapp/test-config-assets/mainnet/arg.did b/scripts/nns-dapp/test-config-assets/mainnet/arg.did index 9d045bd37df..d665c4f434e 100644 --- a/scripts/nns-dapp/test-config-assets/mainnet/arg.did +++ b/scripts/nns-dapp/test-config-assets/mainnet/arg.did @@ -10,7 +10,7 @@ record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" }; record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" }; record{ 0="DFX_NETWORK"; 1="mainnet" }; - record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" }; + record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" }; record{ 0="FETCH_ROOT_KEY"; 1="false" }; record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" }; record{ 0="HOST"; 1="https://icp-api.io" }; diff --git a/scripts/nns-dapp/test-config-assets/mainnet/env b/scripts/nns-dapp/test-config-assets/mainnet/env index a429fa8f7e4..dd40cfdc387 100644 --- a/scripts/nns-dapp/test-config-assets/mainnet/env +++ b/scripts/nns-dapp/test-config-assets/mainnet/env @@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai VITE_OWN_CANISTER_ID=qoctq-giaaa-aaaaa-aaaea-cai VITE_FETCH_ROOT_KEY=false -VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" +VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" VITE_HOST=https://icp-api.io VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/ VITE_AGGREGATOR_CANISTER_URL=https://3r4gx-wqaaa-aaaaq-aaaia-cai.icp0.io From 9164fcf348ff992cec1fa70eb6b142d5c4c237e0 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 20:30:03 +0200 Subject: [PATCH 091/161] Temporary suppress load imported tokens error toast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise, it’s always present in the CI, which breaks the e2e tests. --- frontend/src/lib/services/imported-tokens.services.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index 37317ec3a23..484350a4895 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -54,10 +54,11 @@ export const loadImportedTokens = async () => { // Explicitly handle only UPDATE errors importedTokensStore.reset(); - toastsError({ - labelKey: "error__imported_tokens.load_imported_tokens", - err, - }); + // TODO: uncomment this after api availability on CI + // toastsError({ + // labelKey: "error__imported_tokens.load_imported_tokens", + // err, + // }); }, logMessage: "Get Imported Tokens", }); From ada4b18f80a126238fc8777829dd666d2cba3150 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 20:58:20 +0200 Subject: [PATCH 092/161] Temporary suppress load imported tokens error toast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise, it’s always present in the CI, which breaks the e2e tests. --- .../src/tests/lib/services/app.services.spec.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/tests/lib/services/app.services.spec.ts b/frontend/src/tests/lib/services/app.services.spec.ts index e41c6d0caaa..f28de7fa085 100644 --- a/frontend/src/tests/lib/services/app.services.spec.ts +++ b/frontend/src/tests/lib/services/app.services.spec.ts @@ -84,15 +84,16 @@ describe("app-services", () => { const toastData = get(toastsStore); + expect(toastData).toHaveLength(0); // The imported tokens error should be shown. // TODO: check if this is the correct behavior - expect(toastData).toHaveLength(1); - expect(toastData).toEqual([ - expect.objectContaining({ - text: "There was an unexpected issue while loading imported tokens. Cannot read properties of undefined (reading 'imported_tokens')", - level: "error", - }), - ]); + // expect(toastData).toHaveLength(1); + // expect(toastData).toEqual([ + // expect.objectContaining({ + // text: "There was an unexpected issue while loading imported tokens. Cannot read properties of undefined (reading 'imported_tokens')", + // level: "error", + // }), + // ]); }); it("should call loadActionableProposals after Sns data is ready", async () => { From 19515aebc0813cbf8892878f18c308114cda7276 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 1 Aug 2024 22:15:22 +0200 Subject: [PATCH 093/161] Temporary suppress load imported tokens error toast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise, it’s always present in the CI, which breaks the e2e tests. --- .../lib/services/imported-tokens.services.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 a00e4448db4..d715e9504c0 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -82,11 +82,13 @@ describe("imported-tokens-services", () => { await loadImportedTokens(); - expect(spyToastError).toBeCalledTimes(1); - expect(spyToastError).toBeCalledWith({ - labelKey: "error__imported_tokens.load_imported_tokens", - err: testError, - }); + // TODO: uncomment this after api availability on CI + expect(spyToastError).toBeCalledTimes(0); + // expect(spyToastError).toBeCalledTimes(1); + // expect(spyToastError).toBeCalledWith({ + // labelKey: "error__imported_tokens.load_imported_tokens", + // err: testError, + // }); }); it("should reset store on error", async () => { From ac5947dec2f35977fd3cc468340ce1a34ba57ea5 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 4 Aug 2024 10:33:45 +0200 Subject: [PATCH 094/161] Revert "Temporary suppress load imported tokens error toast" This reverts commit 19515aebc0813cbf8892878f18c308114cda7276. --- .../lib/services/imported-tokens.services.spec.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 d715e9504c0..a00e4448db4 100644 --- a/frontend/src/tests/lib/services/imported-tokens.services.spec.ts +++ b/frontend/src/tests/lib/services/imported-tokens.services.spec.ts @@ -82,13 +82,11 @@ describe("imported-tokens-services", () => { await loadImportedTokens(); - // TODO: uncomment this after api availability on CI - expect(spyToastError).toBeCalledTimes(0); - // expect(spyToastError).toBeCalledTimes(1); - // expect(spyToastError).toBeCalledWith({ - // labelKey: "error__imported_tokens.load_imported_tokens", - // err: testError, - // }); + expect(spyToastError).toBeCalledTimes(1); + expect(spyToastError).toBeCalledWith({ + labelKey: "error__imported_tokens.load_imported_tokens", + err: testError, + }); }); it("should reset store on error", async () => { From c23a67cdf5a20436c84c09f3d3a62e14c6245dc8 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 4 Aug 2024 10:35:31 +0200 Subject: [PATCH 095/161] Revert "Temporary suppress load imported tokens error toast" This reverts commit ada4b18f80a126238fc8777829dd666d2cba3150. --- .../src/tests/lib/services/app.services.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/tests/lib/services/app.services.spec.ts b/frontend/src/tests/lib/services/app.services.spec.ts index f28de7fa085..e41c6d0caaa 100644 --- a/frontend/src/tests/lib/services/app.services.spec.ts +++ b/frontend/src/tests/lib/services/app.services.spec.ts @@ -84,16 +84,15 @@ describe("app-services", () => { const toastData = get(toastsStore); - expect(toastData).toHaveLength(0); // The imported tokens error should be shown. // TODO: check if this is the correct behavior - // expect(toastData).toHaveLength(1); - // expect(toastData).toEqual([ - // expect.objectContaining({ - // text: "There was an unexpected issue while loading imported tokens. Cannot read properties of undefined (reading 'imported_tokens')", - // level: "error", - // }), - // ]); + expect(toastData).toHaveLength(1); + expect(toastData).toEqual([ + expect.objectContaining({ + text: "There was an unexpected issue while loading imported tokens. Cannot read properties of undefined (reading 'imported_tokens')", + level: "error", + }), + ]); }); it("should call loadActionableProposals after Sns data is ready", async () => { From ce4a3117152ff86a1359bf4c83b6192cc9f01263 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 4 Aug 2024 10:35:42 +0200 Subject: [PATCH 096/161] Revert "Temporary suppress load imported tokens error toast" This reverts commit 9164fcf348ff992cec1fa70eb6b142d5c4c237e0. --- frontend/src/lib/services/imported-tokens.services.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/services/imported-tokens.services.ts b/frontend/src/lib/services/imported-tokens.services.ts index 484350a4895..37317ec3a23 100644 --- a/frontend/src/lib/services/imported-tokens.services.ts +++ b/frontend/src/lib/services/imported-tokens.services.ts @@ -54,11 +54,10 @@ export const loadImportedTokens = async () => { // Explicitly handle only UPDATE errors importedTokensStore.reset(); - // TODO: uncomment this after api availability on CI - // toastsError({ - // labelKey: "error__imported_tokens.load_imported_tokens", - // err, - // }); + toastsError({ + labelKey: "error__imported_tokens.load_imported_tokens", + err, + }); }, logMessage: "Get Imported Tokens", }); From 4c3ba3473a6608f995ee9f55f5ad0e26dbe1b2e9 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Sun, 4 Aug 2024 10:35:52 +0200 Subject: [PATCH 097/161] Revert "! ENABLE ENABLE_IMPORT_TOKEN FLAG !" This reverts commit 0179f680782485934d98e3896c17a12b1b25f312. --- dfx.json | 2 +- scripts/nns-dapp/test-config-assets/app/arg.did | 2 +- scripts/nns-dapp/test-config-assets/app/env | 2 +- scripts/nns-dapp/test-config-assets/mainnet/arg.did | 2 +- scripts/nns-dapp/test-config-assets/mainnet/env | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dfx.json b/dfx.json index 8a5dd1e08b3..db88e09e49e 100644 --- a/dfx.json +++ b/dfx.json @@ -372,7 +372,7 @@ "ENABLE_CKBTC": true, "ENABLE_CKTESTBTC": false, "ENABLE_PROJECTS_TABLE": true, - "ENABLE_IMPORT_TOKEN": true + "ENABLE_IMPORT_TOKEN": false } } }, diff --git a/scripts/nns-dapp/test-config-assets/app/arg.did b/scripts/nns-dapp/test-config-assets/app/arg.did index 5f28822458e..5a2452b00a3 100644 --- a/scripts/nns-dapp/test-config-assets/app/arg.did +++ b/scripts/nns-dapp/test-config-assets/app/arg.did @@ -10,7 +10,7 @@ record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" }; record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" }; record{ 0="DFX_NETWORK"; 1="app" }; - record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" }; + record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" }; record{ 0="FETCH_ROOT_KEY"; 1="false" }; record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" }; record{ 0="HOST"; 1="https://icp-api.io" }; diff --git a/scripts/nns-dapp/test-config-assets/app/env b/scripts/nns-dapp/test-config-assets/app/env index 218bffd98bd..9ac6d3f20f4 100644 --- a/scripts/nns-dapp/test-config-assets/app/env +++ b/scripts/nns-dapp/test-config-assets/app/env @@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai VITE_OWN_CANISTER_ID=xnjld-hqaaa-aaaal-qb56q-cai VITE_FETCH_ROOT_KEY=false -VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" +VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" VITE_HOST=https://icp-api.io VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/ VITE_AGGREGATOR_CANISTER_URL=https://otgyv-wyaaa-aaaak-qcgba-cai.icp0.io diff --git a/scripts/nns-dapp/test-config-assets/mainnet/arg.did b/scripts/nns-dapp/test-config-assets/mainnet/arg.did index d665c4f434e..9d045bd37df 100644 --- a/scripts/nns-dapp/test-config-assets/mainnet/arg.did +++ b/scripts/nns-dapp/test-config-assets/mainnet/arg.did @@ -10,7 +10,7 @@ record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" }; record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" }; record{ 0="DFX_NETWORK"; 1="mainnet" }; - record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" }; + record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" }; record{ 0="FETCH_ROOT_KEY"; 1="false" }; record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" }; record{ 0="HOST"; 1="https://icp-api.io" }; diff --git a/scripts/nns-dapp/test-config-assets/mainnet/env b/scripts/nns-dapp/test-config-assets/mainnet/env index dd40cfdc387..a429fa8f7e4 100644 --- a/scripts/nns-dapp/test-config-assets/mainnet/env +++ b/scripts/nns-dapp/test-config-assets/mainnet/env @@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai VITE_OWN_CANISTER_ID=qoctq-giaaa-aaaaa-aaaea-cai VITE_FETCH_ROOT_KEY=false -VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":true}" +VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":true}" VITE_HOST=https://icp-api.io VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/ VITE_AGGREGATOR_CANISTER_URL=https://3r4gx-wqaaa-aaaaq-aaaia-cai.icp0.io From d496799aaff8ad34d2b6e7f943fe5680a9f8b3fa Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 5 Aug 2024 14:18:42 +0200 Subject: [PATCH 098/161] feat: compareTokensByImportance comparator --- frontend/src/lib/utils/tokens-table.utils.ts | 21 ++++---------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/frontend/src/lib/utils/tokens-table.utils.ts b/frontend/src/lib/utils/tokens-table.utils.ts index f7c9b9c8eb1..9789076a4cd 100644 --- a/frontend/src/lib/utils/tokens-table.utils.ts +++ b/frontend/src/lib/utils/tokens-table.utils.ts @@ -21,7 +21,6 @@ export const compareTokensWithBalanceFirst = createDescendingComparator( (token: UserToken) => getTokenBalanceOrZero(token) > 0n ); -// TODO: add unit tests export const compareTokensWithBalanceOrImportedFirst = ({ importedTokenIds, }: { @@ -43,20 +42,9 @@ const ImportantCkTokenIds = [ ] // To place other tokens (which get an index of -1) at the bottom. .reverse(); -// TODO: update unit tests -export const compareTokensByImportance = ({ - importedTokenIds, -}: { - importedTokenIds: Set; -}) => - createDescendingComparator((token: UserToken) => { - const ckKnownIndex = ImportantCkTokenIds.indexOf(token.universeId.toText()); - if (ckKnownIndex >= 0) { - return ckKnownIndex; - } - - return importedTokenIds.has(token.universeId.toText()) ? 0 : -1; - }); +export const compareTokensByImportance = createDescendingComparator( + (token: UserToken) => ImportantCkTokenIds.indexOf(token.universeId.toText()) +); export const compareTokensAlphabetically = createAscendingComparator( ({ title }: UserToken) => title.toLowerCase() @@ -71,7 +59,6 @@ export const compareTokensForTokensTable = ({ mergeComparators([ compareTokensIcpFirst, compareTokensWithBalanceOrImportedFirst({ importedTokenIds }), - compareTokensWithBalanceFirst, - compareTokensByImportance({ importedTokenIds }), + compareTokensByImportance, compareTokensAlphabetically, ]); From 5c58c172c7c47cb79883903fbc2457f75d8d321f Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 5 Aug 2024 14:19:25 +0200 Subject: [PATCH 099/161] refactor: tokenWithBalance mock function to have id --- .../lib/utils/tokens-table.utils.spec.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts index f0b8b821ce6..a1a982d3120 100644 --- a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts +++ b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts @@ -17,12 +17,12 @@ import { import { TokenAmountV2 } from "@dfinity/utils"; describe("tokens-table.utils", () => { - const tokenWithBalance = (amount: bigint) => + const tokenWithBalance = ({ id, amount }: { id: number; amount: bigint }) => createUserToken({ - universeId: principal(Number(amount)), + universeId: principal(id), balance: TokenAmountV2.fromUlps({ amount, - token: { name: `T${amount}`, symbol: `T${amount}`, decimals: 8 }, + token: { name: `T${id}`, symbol: `T${id}`, decimals: 8 }, }), }); const ckBTCToken = createUserToken({ @@ -54,26 +54,26 @@ describe("tokens-table.utils", () => { it("should keep tokens with balance first", () => { expect( compareTokensWithBalanceFirst( - tokenWithBalance(1n), - tokenWithBalance(0n) + tokenWithBalance({ id: 1, amount: 1n }), + tokenWithBalance({ id: 0, amount: 0n }) ) ).toEqual(-1); expect( compareTokensWithBalanceFirst( - tokenWithBalance(0n), - tokenWithBalance(1n) + tokenWithBalance({ id: 0, amount: 0n }), + tokenWithBalance({ id: 1, amount: 1n }) ) ).toEqual(1); expect( compareTokensWithBalanceFirst( - tokenWithBalance(0n), - tokenWithBalance(0n) + tokenWithBalance({ id: 0, amount: 0n }), + tokenWithBalance({ id: 0, amount: 0n }) ) ).toEqual(0); expect( compareTokensWithBalanceFirst( - tokenWithBalance(1n), - tokenWithBalance(1n) + tokenWithBalance({ id: 1, amount: 1n }), + tokenWithBalance({ id: 1, amount: 1n }) ) ).toEqual(0); }); @@ -82,12 +82,12 @@ describe("tokens-table.utils", () => { expect( compareTokensWithBalanceFirst( createUserTokenLoading(), - tokenWithBalance(1n) + tokenWithBalance({ id: 1, amount: 1n }) ) ).toEqual(1); expect( compareTokensWithBalanceFirst( - tokenWithBalance(1n), + tokenWithBalance({ id: 1, amount: 1n }), createUserTokenLoading() ) ).toEqual(-1); @@ -96,21 +96,21 @@ describe("tokens-table.utils", () => { describe("compareTokensByImportance", () => { it("should sort tokens by importance", () => { - const token0 = tokenWithBalance(0n); + const token0 = tokenWithBalance({ id: 0, amount: 0n }); const expectedOrder = [ckBTCToken, ckETHTToken, ckUSDCToken, token0]; expect( [token0, ckUSDCToken, ckETHTToken, ckBTCToken].sort( - compareTokensByImportance({ importedTokenIds: new Set() }) + compareTokensByImportance ) ).toEqual(expectedOrder); expect( [ckBTCToken, ckETHTToken, ckUSDCToken, token0].sort( - compareTokensByImportance({ importedTokenIds: new Set() }) + compareTokensByImportance ) ).toEqual(expectedOrder); expect( [ckETHTToken, ckBTCToken, token0, ckUSDCToken].sort( - compareTokensByImportance({ importedTokenIds: new Set() }) + compareTokensByImportance ) ).toEqual(expectedOrder); }); From eb50ce17fe66dab0194083773a0a6ede24525831 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 5 Aug 2024 14:19:35 +0200 Subject: [PATCH 100/161] test: compareTokensWithBalanceOrImportedFirst --- .../lib/utils/tokens-table.utils.spec.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts index a1a982d3120..4c8e213aca5 100644 --- a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts +++ b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts @@ -6,6 +6,7 @@ import { compareTokensByImportance, compareTokensIcpFirst, compareTokensWithBalanceFirst, + compareTokensWithBalanceOrImportedFirst, } from "$lib/utils/tokens-table.utils"; import { principal } from "$tests/mocks/sns-projects.mock"; import { @@ -116,6 +117,76 @@ describe("tokens-table.utils", () => { }); }); + describe("compareTokensWithBalanceOrImportedFirst", () => { + const token0 = tokenWithBalance({ id: 0, amount: 0n }); + const token1 = tokenWithBalance({ id: 1, amount: 1n }); + const importedTokenWithBalance = tokenWithBalance({ id: 2, amount: 1n }); + const importedTokenNoBalance = tokenWithBalance({ id: 3, amount: 0n }); + const importedTokenIds = new Set([ + importedTokenWithBalance.universeId.toText(), + importedTokenNoBalance.universeId.toText(), + ]); + + it("should compare by balance", () => { + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(token1, token0) + ).toEqual(-1); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(token0, token1) + ).toEqual(1); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(token1, token1) + ).toEqual(0); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(token0, token0) + ).toEqual(0); + }); + + it("should compare by imported", () => { + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(importedTokenNoBalance, token0) + ).toEqual(-1); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(token0, importedTokenNoBalance) + ).toEqual(1); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(importedTokenWithBalance, importedTokenNoBalance) + ).toEqual(0); + }); + + it("should compare by balance and imported", () => { + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(importedTokenNoBalance, token1) + ).toEqual(0); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(token0, importedTokenNoBalance) + ).toEqual(1); + expect( + compareTokensWithBalanceOrImportedFirst({ + importedTokenIds, + })(importedTokenWithBalance, token0) + ).toEqual(-1); + }); + }); + describe("compareTokensAlphabetically", () => { const annaToken = createUserToken({ title: "Anna" }); const arnyToken = createUserToken({ title: "Arny" }); From ee63a31e9331401fe9913cb79b3aae29020f2b9d Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Mon, 5 Aug 2024 14:20:34 +0200 Subject: [PATCH 101/161] Remove compareTokensWithBalanceFirst --- frontend/src/lib/utils/tokens-table.utils.ts | 4 -- .../lib/utils/tokens-table.utils.spec.ts | 46 ------------------- 2 files changed, 50 deletions(-) diff --git a/frontend/src/lib/utils/tokens-table.utils.ts b/frontend/src/lib/utils/tokens-table.utils.ts index 9789076a4cd..52898bfcfb9 100644 --- a/frontend/src/lib/utils/tokens-table.utils.ts +++ b/frontend/src/lib/utils/tokens-table.utils.ts @@ -17,10 +17,6 @@ export const compareTokensIcpFirst = createDescendingComparator( (token: UserToken) => token.universeId.toText() === OWN_CANISTER_ID_TEXT ); -export const compareTokensWithBalanceFirst = createDescendingComparator( - (token: UserToken) => getTokenBalanceOrZero(token) > 0n -); - export const compareTokensWithBalanceOrImportedFirst = ({ importedTokenIds, }: { diff --git a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts index 4c8e213aca5..daaa2d41388 100644 --- a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts +++ b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts @@ -5,7 +5,6 @@ import { compareTokensAlphabetically, compareTokensByImportance, compareTokensIcpFirst, - compareTokensWithBalanceFirst, compareTokensWithBalanceOrImportedFirst, } from "$lib/utils/tokens-table.utils"; import { principal } from "$tests/mocks/sns-projects.mock"; @@ -13,7 +12,6 @@ import { ckBTCTokenBase, createIcpUserToken, createUserToken, - createUserTokenLoading, } from "$tests/mocks/tokens-page.mock"; import { TokenAmountV2 } from "@dfinity/utils"; @@ -51,50 +49,6 @@ describe("tokens-table.utils", () => { }); }); - describe("compareTokensWithBalanceFirst", () => { - it("should keep tokens with balance first", () => { - expect( - compareTokensWithBalanceFirst( - tokenWithBalance({ id: 1, amount: 1n }), - tokenWithBalance({ id: 0, amount: 0n }) - ) - ).toEqual(-1); - expect( - compareTokensWithBalanceFirst( - tokenWithBalance({ id: 0, amount: 0n }), - tokenWithBalance({ id: 1, amount: 1n }) - ) - ).toEqual(1); - expect( - compareTokensWithBalanceFirst( - tokenWithBalance({ id: 0, amount: 0n }), - tokenWithBalance({ id: 0, amount: 0n }) - ) - ).toEqual(0); - expect( - compareTokensWithBalanceFirst( - tokenWithBalance({ id: 1, amount: 1n }), - tokenWithBalance({ id: 1, amount: 1n }) - ) - ).toEqual(0); - }); - - it("should treat token in loading state as having a balance of 0", () => { - expect( - compareTokensWithBalanceFirst( - createUserTokenLoading(), - tokenWithBalance({ id: 1, amount: 1n }) - ) - ).toEqual(1); - expect( - compareTokensWithBalanceFirst( - tokenWithBalance({ id: 1, amount: 1n }), - createUserTokenLoading() - ) - ).toEqual(-1); - }); - }); - describe("compareTokensByImportance", () => { it("should sort tokens by importance", () => { const token0 = tokenWithBalance({ id: 0, amount: 0n }); From ae8918a61b9fe8f6305e7423e3d42cb1b9988d62 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 6 Aug 2024 11:00:27 +0200 Subject: [PATCH 102/161] feat: handle import token error --- .../lib/modals/accounts/ImportTokenModal.svelte | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index a0f544baa63..c8a3f34c43d 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -94,7 +94,7 @@ labelKey: "import_token.importing", }); - await addImportedToken({ + const { success } = await addImportedToken({ tokenToAdd: { ledgerCanisterId, indexCanisterId, @@ -102,13 +102,15 @@ importedTokens: $importedTokensStore.importedTokens, }); - dispatch("nnsClose"); + if (success) { + dispatch("nnsClose"); - goto( - buildWalletUrl({ - universe: ledgerCanisterId.toText(), - }) - ); + goto( + buildWalletUrl({ + universe: ledgerCanisterId.toText(), + }) + ); + } stopBusy("import-token-importing"); }; From 201ab7a75733c754c195542e06ad42aa118b79a5 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 6 Aug 2024 11:21:24 +0200 Subject: [PATCH 103/161] test: createTokenWithBalance util --- .../lib/utils/tokens-table.utils.spec.ts | 27 +++++++++---------- frontend/src/tests/mocks/tokens-page.mock.ts | 19 +++++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts index daaa2d41388..7ede83f1723 100644 --- a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts +++ b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts @@ -7,23 +7,14 @@ import { compareTokensIcpFirst, compareTokensWithBalanceOrImportedFirst, } from "$lib/utils/tokens-table.utils"; -import { principal } from "$tests/mocks/sns-projects.mock"; import { ckBTCTokenBase, createIcpUserToken, + createTokenWithBalance, createUserToken, } from "$tests/mocks/tokens-page.mock"; -import { TokenAmountV2 } from "@dfinity/utils"; describe("tokens-table.utils", () => { - const tokenWithBalance = ({ id, amount }: { id: number; amount: bigint }) => - createUserToken({ - universeId: principal(id), - balance: TokenAmountV2.fromUlps({ - amount, - token: { name: `T${id}`, symbol: `T${id}`, decimals: 8 }, - }), - }); const ckBTCToken = createUserToken({ universeId: CKBTC_UNIVERSE_CANISTER_ID, }); @@ -51,7 +42,7 @@ describe("tokens-table.utils", () => { describe("compareTokensByImportance", () => { it("should sort tokens by importance", () => { - const token0 = tokenWithBalance({ id: 0, amount: 0n }); + const token0 = createTokenWithBalance({ id: 0, amount: 0n }); const expectedOrder = [ckBTCToken, ckETHTToken, ckUSDCToken, token0]; expect( [token0, ckUSDCToken, ckETHTToken, ckBTCToken].sort( @@ -72,10 +63,16 @@ describe("tokens-table.utils", () => { }); describe("compareTokensWithBalanceOrImportedFirst", () => { - const token0 = tokenWithBalance({ id: 0, amount: 0n }); - const token1 = tokenWithBalance({ id: 1, amount: 1n }); - const importedTokenWithBalance = tokenWithBalance({ id: 2, amount: 1n }); - const importedTokenNoBalance = tokenWithBalance({ id: 3, amount: 0n }); + const token0 = createTokenWithBalance({ id: 0, amount: 0n }); + const token1 = createTokenWithBalance({ id: 1, amount: 1n }); + const importedTokenWithBalance = createTokenWithBalance({ + id: 2, + amount: 1n, + }); + const importedTokenNoBalance = createTokenWithBalance({ + id: 3, + amount: 0n, + }); const importedTokenIds = new Set([ importedTokenWithBalance.universeId.toText(), importedTokenNoBalance.universeId.toText(), diff --git a/frontend/src/tests/mocks/tokens-page.mock.ts b/frontend/src/tests/mocks/tokens-page.mock.ts index 3179260f21e..8a28de58d50 100644 --- a/frontend/src/tests/mocks/tokens-page.mock.ts +++ b/frontend/src/tests/mocks/tokens-page.mock.ts @@ -152,6 +152,25 @@ export const createUserToken = (params: Partial = {}) => { }; }; +export const createTokenWithBalance = ({ + id, + amount, +}: { + id: number; + amount: bigint; +}) => + createUserToken({ + universeId: principal(id), + balance: TokenAmountV2.fromUlps({ + amount, + token: { + name: `T${id}`, + symbol: `T${id}`, + decimals: 8, + }, + }), + }) as UserTokenData; + export const createIcpUserToken = (params: Partial = {}) => ({ ...icpTokenNoBalance, ...params, From 7b2714bc2dc0c13e40e0f641aae4e8c172782243 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 6 Aug 2024 15:48:55 +0200 Subject: [PATCH 104/161] test: should display imported tokens after important with balance --- .../src/tests/routes/app/tokens/page.spec.ts | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/frontend/src/tests/routes/app/tokens/page.spec.ts b/frontend/src/tests/routes/app/tokens/page.spec.ts index dd30e39844f..335a1b03243 100644 --- a/frontend/src/tests/routes/app/tokens/page.spec.ts +++ b/frontend/src/tests/routes/app/tokens/page.spec.ts @@ -17,7 +17,10 @@ import { import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store"; import { icrcAccountsStore } from "$lib/stores/icrc-accounts.store"; import { icrcCanistersStore } from "$lib/stores/icrc-canisters.store"; +import { importedTokensStore } from "$lib/stores/imported-tokens.store"; import { tokensStore } from "$lib/stores/tokens.store"; +import type { IcrcTokenMetadata } from "$lib/types/icrc"; +import type { ImportedTokenData } from "$lib/types/imported-tokens"; import { numberToUlps } from "$lib/utils/token.utils"; import TokensRoute from "$routes/(app)/(nns)/tokens/+page.svelte"; import { @@ -89,6 +92,20 @@ describe("Tokens route", () => { pending_utxos: [], required_confirmations: 0, }); + const ckToken1Id = principal(100); + const ckToken1Metadata = { + name: "ZTOKEN1", + symbol: "ZTOKEN1", + fee: 4_000n, + decimals: 6, + } as IcrcTokenMetadata; + const ckToken2Id = principal(101); + const ckToken2Metadata = { + name: "ATOKEN2", + symbol: "ATOKEN2", + fee: 4_000n, + decimals: 6, + } as IcrcTokenMetadata; const renderPage = async () => { const { container } = render(TokensRoute); @@ -115,6 +132,9 @@ describe("Tokens route", () => { [CKETH_UNIVERSE_CANISTER_ID.toText()]: mockCkETHToken, [CKETHSEPOLIA_UNIVERSE_CANISTER_ID.toText()]: mockCkTESTBTCToken, [CKUSDC_UNIVERSE_CANISTER_ID.toText()]: mockCkUSDCToken, + // imported tokens + [ckToken1Id.toText()]: ckToken1Metadata, + [ckToken2Id.toText()]: ckToken2Metadata, }; if (isNullish(tokenMap[canisterId.toText()])) { throw new Error( @@ -507,7 +527,7 @@ describe("Tokens route", () => { ]); }); - describe("taking balance into account", () => { + describe("taking balance and import tokens into account", () => { beforeEach(() => { vi.spyOn(icrcLedgerApi, "queryIcrcBalance").mockImplementation( async ({ canisterId }) => { @@ -545,6 +565,86 @@ describe("Tokens route", () => { }); }); + describe("imported tokens", () => { + const ckToken1Data: ImportedTokenData = { + ledgerCanisterId: ckToken1Id, + indexCanisterId: principal(111), + }; + const ckToken2Data: ImportedTokenData = { + ledgerCanisterId: ckToken2Id, + indexCanisterId: undefined, + }; + + beforeEach(() => { + importedTokensStore.reset(); + + vi.spyOn(icrcLedgerApi, "queryIcrcBalance").mockImplementation( + async ({ canisterId }) => { + const balancesMap = { + [CKBTC_UNIVERSE_CANISTER_ID.toText()]: ckBTCBalanceE8s, + [CKTESTBTC_UNIVERSE_CANISTER_ID.toText()]: ckBTCBalanceE8s, + [CKETH_UNIVERSE_CANISTER_ID.toText()]: 0n, + [CKUSDC_UNIVERSE_CANISTER_ID.toText()]: ckUSDCBalanceE8s, + [CKETHSEPOLIA_UNIVERSE_CANISTER_ID.toText()]: + ckETHBalanceUlps, + [ledgerCanisterIdTetris.toText()]: tetrisBalanceE8s, + [ledgerCanisterIdPacman.toText()]: 0n, + [ckToken1Id.toText()]: 10n, + [ckToken2Id.toText()]: 0n, + }; + if (isNullish(balancesMap[canisterId.toText()])) { + throw new Error( + `Account not found for canister ${canisterId.toText()}` + ); + } + return balancesMap[canisterId.toText()]; + } + ); + + // Add 2 imported tokens + tokensStore.setToken({ + canisterId: ckToken1Id, + token: ckToken1Metadata, + }); + icrcCanistersStore.setCanisters({ + ledgerCanisterId: ckToken1Id, + indexCanisterId: undefined, + }); + tokensStore.setToken({ + canisterId: ckToken2Id, + token: ckToken2Metadata, + }); + icrcCanistersStore.setCanisters({ + ledgerCanisterId: ckToken2Id, + indexCanisterId: undefined, + }); + + importedTokensStore.set({ + importedTokens: [ckToken1Data, ckToken2Data], + certified: true, + }); + }); + + it("should display imported tokens after important with balance", async () => { + const po = await renderPage(); + const tokensPagePo = po.getTokensPagePo(); + expect(await tokensPagePo.getTokenNames()).toEqual([ + "Internet Computer", + // ck with balance + "ckBTC", + "ckUSDC", + // Imported tokens should be placed with the SNS tokens that have a non-zero balance + // and should be sorted alphabetically. + "ATOKEN2", // Imported + "Tetris", // SNS with balance + "ZTOKEN1", // Imported + // Zero balance + "ckETH", + "Pacman", + ]); + }); + }); + describe("when logged out", () => { beforeEach(() => { setNoIdentity(); From f52928ffdd1a14068196215467e3d2f22b26ab06 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 6 Aug 2024 16:04:00 +0200 Subject: [PATCH 105/161] refactor: don't export createTokenWithBalance --- .../lib/utils/tokens-table.utils.spec.ts | 22 ++++++++++++++++++- frontend/src/tests/mocks/tokens-page.mock.ts | 19 ---------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts index 7ede83f1723..7b9c75c3fa9 100644 --- a/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts +++ b/frontend/src/tests/lib/utils/tokens-table.utils.spec.ts @@ -1,18 +1,20 @@ import { CKBTC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckbtc-canister-ids.constants"; import { CKETH_UNIVERSE_CANISTER_ID } from "$lib/constants/cketh-canister-ids.constants"; import { CKUSDC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckusdc-canister-ids.constants"; +import type { UserTokenData } from "$lib/types/tokens-page"; import { compareTokensAlphabetically, compareTokensByImportance, compareTokensIcpFirst, compareTokensWithBalanceOrImportedFirst, } from "$lib/utils/tokens-table.utils"; +import { principal } from "$tests/mocks/sns-projects.mock"; import { ckBTCTokenBase, createIcpUserToken, - createTokenWithBalance, createUserToken, } from "$tests/mocks/tokens-page.mock"; +import { TokenAmountV2 } from "@dfinity/utils"; describe("tokens-table.utils", () => { const ckBTCToken = createUserToken({ @@ -24,6 +26,24 @@ describe("tokens-table.utils", () => { const ckUSDCToken = createUserToken({ universeId: CKUSDC_UNIVERSE_CANISTER_ID, }); + const createTokenWithBalance = ({ + id, + amount, + }: { + id: number; + amount: bigint; + }) => + createUserToken({ + universeId: principal(id), + balance: TokenAmountV2.fromUlps({ + amount, + token: { + name: `T${id}`, + symbol: `T${id}`, + decimals: 8, + }, + }), + }) as UserTokenData; beforeEach(() => { vi.clearAllMocks(); diff --git a/frontend/src/tests/mocks/tokens-page.mock.ts b/frontend/src/tests/mocks/tokens-page.mock.ts index 8a28de58d50..3179260f21e 100644 --- a/frontend/src/tests/mocks/tokens-page.mock.ts +++ b/frontend/src/tests/mocks/tokens-page.mock.ts @@ -152,25 +152,6 @@ export const createUserToken = (params: Partial = {}) => { }; }; -export const createTokenWithBalance = ({ - id, - amount, -}: { - id: number; - amount: bigint; -}) => - createUserToken({ - universeId: principal(id), - balance: TokenAmountV2.fromUlps({ - amount, - token: { - name: `T${id}`, - symbol: `T${id}`, - decimals: 8, - }, - }), - }) as UserTokenData; - export const createIcpUserToken = (params: Partial = {}) => ({ ...icpTokenNoBalance, ...params, From 06e5be28c5b8725355ba31c0d9a227798d6c0a99 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 8 Aug 2024 15:51:33 +0200 Subject: [PATCH 106/161] Expose testId and require props for PrincipaInput component --- frontend/src/lib/components/ui/PrincipalInput.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/lib/components/ui/PrincipalInput.svelte b/frontend/src/lib/components/ui/PrincipalInput.svelte index baedf068c4b..9c224c1ddd6 100644 --- a/frontend/src/lib/components/ui/PrincipalInput.svelte +++ b/frontend/src/lib/components/ui/PrincipalInput.svelte @@ -7,6 +7,8 @@ export let placeholderLabelKey: string; export let name: string; export let principal: Principal | undefined = undefined; + export let required: boolean | undefined = undefined; + export let testId: string | undefined = undefined; let address = principal?.toText() ?? ""; $: principal = getPrincipalFromString(address); @@ -23,10 +25,12 @@ inputType="text" {placeholderLabelKey} {name} + {testId} bind:value={address} errorMessage={showError ? $i18n.error.principal_not_valid : undefined} on:blur={showErrorIfAny} showInfo={$$slots.label !== undefined} + {required} > From 37106ed2e8029e206e2bd77c47c1616fc8376bbe Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 8 Aug 2024 17:05:37 +0200 Subject: [PATCH 107/161] Update labels --- frontend/src/lib/i18n/en.json | 10 ++++++++++ frontend/src/lib/types/i18n.d.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 6641de6b8a6..96cbc5a3b8b 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1043,5 +1043,15 @@ "import_token": "Import Token", "add_imported_token_success": "New token has been successfully imported!", "remove_imported_token_success": "The token has been successfully removed!" + }, + "import_token": { + "description": "To import a new token to your NNS dapp wallet, you will need to find, and paste the ledger canister id of the token. If you want to see your transaction history, you need to import the token’s index canister.", + "ledger_label": "Ledger Canister ID", + "index_label_optional": "Index Canister ID (Optional)", + "index_label": "Index Canister ID", + "placeholder": "00000-00000-00000-00000-000", + "index_canister_description": "Index Canister allows to display a token balance and transaction history. Note: not all tokens have index canisters.", + "review_token_info": "Review token info", + "warning": "Warning: Be careful what token you import! Anyone can create a token including one with the same name as existing tokens, such as ckBTC." } } diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 64759a50e4c..5ab094ea91a 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1105,6 +1105,17 @@ interface I18nTokens { remove_imported_token_success: string; } +interface I18nImport_token { + description: string; + ledger_label: string; + index_label_optional: string; + index_label: string; + placeholder: string; + index_canister_description: string; + review_token_info: string; + warning: string; +} + interface I18nNeuron_state { Unspecified: string; Locked: string; @@ -1389,6 +1400,7 @@ interface I18n { settings: I18nSettings; sync: I18nSync; tokens: I18nTokens; + import_token: I18nImport_token; neuron_state: I18nNeuron_state; topics: I18nTopics; topics_description: I18nTopics_description; From ac57c9cc0320800cdaf5d32e52fdd2174028ea7a Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 8 Aug 2024 17:06:00 +0200 Subject: [PATCH 108/161] New ImportTokenForm component --- .../accounts/ImportTokenForm.svelte | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 frontend/src/lib/components/accounts/ImportTokenForm.svelte diff --git a/frontend/src/lib/components/accounts/ImportTokenForm.svelte b/frontend/src/lib/components/accounts/ImportTokenForm.svelte new file mode 100644 index 00000000000..b2901a250e1 --- /dev/null +++ b/frontend/src/lib/components/accounts/ImportTokenForm.svelte @@ -0,0 +1,72 @@ + + + +

{$i18n.import_token.description}

+ +
dispatch("nnsSubmit")}> + + {$i18n.import_token.ledger_label} + + + + + + +

+ +

+ + + +
+ + + +
+ +
+ + From de7ed16044e3ef2c3ca1349171a366c13dc96954 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 8 Aug 2024 17:06:19 +0200 Subject: [PATCH 109/161] New ImportTokenModal component --- .../modals/accounts/ImportTokenModal.svelte | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 frontend/src/lib/modals/accounts/ImportTokenModal.svelte diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte new file mode 100644 index 00000000000..ae38debe4fa --- /dev/null +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -0,0 +1,58 @@ + + + + {currentStep?.title} + + {#if currentStep?.name === STEP_FORM} + + {/if} + {#if currentStep?.name === STEP_REVIEW && nonNullish(ledgerCanisterId) && nonNullish(tokenMetaData)} + TBD: Review imported token + {/if} + From 245d9142130744b7cb444dc85543df1033d55944 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Thu, 8 Aug 2024 17:08:10 +0200 Subject: [PATCH 110/161] feat: show modal on "Import token" button click --- frontend/src/lib/pages/Tokens.svelte | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index 6c11737f20d..dc553e9e3bc 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -11,6 +11,7 @@ import { Popover } from "@dfinity/gix-components"; import { TokenAmountV2 } from "@dfinity/utils"; import { ENABLE_IMPORT_TOKEN } from "$lib/stores/feature-flags.store"; + import ImportTokenModal from "$lib/modals/accounts/ImportTokenModal.svelte"; export let userTokensData: UserToken[]; @@ -41,9 +42,7 @@ hideZeroBalancesStore.set("show"); }; - const importToken = async () => { - // TBD: Implement import token. - }; + let showImportTokenModal = false; // TODO(Import token): After removing ENABLE_IMPORT_TOKEN combine divs ->
@@ -82,7 +81,7 @@ @@ -112,6 +111,10 @@ > + + {#if showImportTokenModal} + (showImportTokenModal = false)} /> + {/if} From 6f092a4b56272ec5491d613a700fa3d20dd495a1 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 13 Aug 2024 11:34:56 +0200 Subject: [PATCH 128/161] reset package lock --- frontend/package-lock.json | 125 +++++++++++++++++-------------------- frontend/package.json | 6 +- 2 files changed, 60 insertions(+), 71 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f10f6c260d1..044e992ca74 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dfinity/nns-dapp", - "version": "2.0.85", + "version": "2.0.86", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@dfinity/nns-dapp", - "version": "2.0.85", + "version": "2.0.86", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@dfinity/agent": "^1.4.0", @@ -250,10 +250,9 @@ } }, "node_modules/@dfinity/ckbtc": { - "version": "2.5.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-2.5.0-next-2024-07-30.tgz", - "integrity": "sha512-Oj1hGhfv8kDP8Dq+QmPh3Gm6pHMJNnbYqNJ0pLHDyjnw7H+9XrHfFRvew+iPQ1YuyHQg+WjtuqY7Ph87TJCGFQ==", - "license": "Apache-2.0", + "version": "2.5.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-2.5.0-next-2024-08-10.tgz", + "integrity": "sha512-rAhnQ9i4lDHAOx+axm8m3NwqGLZFFa4BayDis+bEYp8ufkCgqDjF6v3BeJU3pN7dehUKpg97t8mA0rHGPYFE+A==", "dependencies": { "@noble/hashes": "^1.3.2", "base58-js": "^1.0.5", @@ -267,10 +266,9 @@ } }, "node_modules/@dfinity/cmc": { - "version": "3.1.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.1.0-next-2024-07-30.tgz", - "integrity": "sha512-d9TtZxI1fuQ0QzogMSTLfyy/WtCTahWgms0R9Htd85z3jZWVJ1+jH85MuG1kfPBQPsLH99izdcM96JlBTqv20w==", - "license": "Apache-2.0", + "version": "3.1.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.1.0-next-2024-08-10.tgz", + "integrity": "sha512-msb4dX0wmdfSaTIVred1NkfNOdLonwzQ83RVF1O/Y0U1q++NHFzMAsFc5hS4sig0hCcIgMw/HJ1ZXhNgweBfCw==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -279,9 +277,9 @@ } }, "node_modules/@dfinity/gix-components": { - "version": "4.6.0-next-2024-08-06", - "resolved": "https://registry.npmjs.org/@dfinity/gix-components/-/gix-components-4.6.0-next-2024-08-06.tgz", - "integrity": "sha512-oic5/+OqocCRoTBHIcsrrgrpBPDYYSytMcDyYtl1w7nW6ObOxoJig5IM9IBt8w0+J64S67rWnIA+UwQXUK1ufQ==", + "version": "4.6.0-next-2024-08-12", + "resolved": "https://registry.npmjs.org/@dfinity/gix-components/-/gix-components-4.6.0-next-2024-08-12.tgz", + "integrity": "sha512-KjVlUoADfuPKtl4YXfodMINXCmDTxpx5kjmddWwhDc2fxbdoiNdM5V2wkC3QzzYZgxC78QZY+AsMHr4F1JqZTg==", "license": "Apache-2.0", "dependencies": { "dompurify": "^3.1.6", @@ -294,10 +292,9 @@ } }, "node_modules/@dfinity/ic-management": { - "version": "5.1.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.1.0-next-2024-07-30.tgz", - "integrity": "sha512-FvjFqlOMpQ6x58sl9b/B9AEhZ+M8LwZK7Dxe2kKjzroBQ7yFVfuujkogICzk6p/xV6dlMuT/lVNTfFb2JEwd6g==", - "license": "Apache-2.0", + "version": "5.1.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.1.0-next-2024-08-10.tgz", + "integrity": "sha512-MVeiGQQTs2cKh1myzwEAZYK0ihyu8tFgToEqeUrP000830cRpkBpAmAde4GzmpeA9aRHatYUrvH1Mtu9L/Gr1A==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -321,10 +318,9 @@ } }, "node_modules/@dfinity/ledger-icp": { - "version": "2.4.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.4.0-next-2024-07-30.tgz", - "integrity": "sha512-pma0yUdZjyjD+L2lJEtt5YQ3U7OgQ/dGRfyNeECsrZxTXTwJbNR1QrqNzpJQ9pOGaLf7mKcMHb6dUxwdjV63Eg==", - "license": "Apache-2.0", + "version": "2.4.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.4.0-next-2024-08-10.tgz", + "integrity": "sha512-aVpUCrvH1/rZ1Us6d42czdSp40/sSXoi6I21lFajDXqOG4Cpqduos/4ICHyAd5rxcWz7DvzmHUGJMgl0FCvJGA==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -333,10 +329,9 @@ } }, "node_modules/@dfinity/ledger-icrc": { - "version": "2.4.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.4.0-next-2024-07-30.tgz", - "integrity": "sha512-CJnq4XquTa5eUlOV5eqMJhQWOA2ClUepm9ooTrxse18ka/6r/axb4JEpwK+1Ee7iyNJGa5Ij135bHdc2w2Qw1A==", - "license": "Apache-2.0", + "version": "2.4.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.4.0-next-2024-08-10.tgz", + "integrity": "sha512-jK/Xz8uCBBQ6QnQs0vJp17GO7q4VACMyOgvdvo4mDpdhAasEaceqOGeaghlU+E8nYqD9nH/ZPHKYa79Gai0j2g==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -345,10 +340,9 @@ } }, "node_modules/@dfinity/nns": { - "version": "5.2.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-5.2.0-next-2024-07-30.tgz", - "integrity": "sha512-VfaLTP15hFiZOh7moTKAgTzCMSMdYCZuP8v0piJ4qLkOIwdlhJMpobf97X2MedL5oaOkTISsU6rKyH5X+RwKmg==", - "license": "Apache-2.0", + "version": "5.2.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-5.2.0-next-2024-08-10.tgz", + "integrity": "sha512-WvD9OX9LzrOuExi8HPbGKUUZKkuy7i2rRSNUwcVHsN4pahio6HncTnKLLkgIQ2nf7iGHntCvTtJ00Xz3E6iKzg==", "dependencies": { "@noble/hashes": "^1.3.2", "randombytes": "^2.1.0" @@ -370,10 +364,9 @@ } }, "node_modules/@dfinity/sns": { - "version": "3.1.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.1.0-next-2024-07-30.tgz", - "integrity": "sha512-lG25m6F8capAVZTdSX07rq3OwbxoNNtaV+jwBrlxX2TO14u0bLrfJ5EiM101wIxfaomF8O/bNJSgrtyPB2Qemg==", - "license": "Apache-2.0", + "version": "3.1.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.1.0-next-2024-08-10.tgz", + "integrity": "sha512-D9B/+/IcKEz4TriOoh1q+k3VfWPt6FfwMjFMBlRGHcrOsgut8G5P0TUsK03dX50muLGLRpxwjWMAfF0sI04vjQ==", "dependencies": { "@noble/hashes": "^1.3.2" }, @@ -386,10 +379,9 @@ } }, "node_modules/@dfinity/utils": { - "version": "2.4.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.4.0-next-2024-07-30.tgz", - "integrity": "sha512-e+S6n4JX+ZtF1HzjG32tfVLWM2TMY0Xcbf0sRXmgx4S+DmKr8NA705dq4MmCike/W4RhT2jEljoNIixZwhkV1g==", - "license": "Apache-2.0", + "version": "2.4.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.4.0-next-2024-08-10.tgz", + "integrity": "sha512-17YrSzkw1E5pIq/yUYKraItdpXhu9XIS5+CX52a8xkChQYkKR4wpQu1r6a9wW53NZq+LOsa+lAl/clJAzTJi7g==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -2030,7 +2022,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-1.0.5.tgz", "integrity": "sha512-LkkAPP8Zu+c0SVNRTRVDyMfKVORThX+rCViget00xdgLRrKkClCTz1T7cIrpr69ShwV5XJuuoZvMvJ43yURwkA==", - "license": "MIT", "engines": { "node": ">= 8" } @@ -2065,8 +2056,7 @@ "node_modules/bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", - "license": "MIT" + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, "node_modules/bignumber.js": { "version": "9.1.1", @@ -5389,7 +5379,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -7044,9 +7033,9 @@ "requires": {} }, "@dfinity/ckbtc": { - "version": "2.5.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-2.5.0-next-2024-07-30.tgz", - "integrity": "sha512-Oj1hGhfv8kDP8Dq+QmPh3Gm6pHMJNnbYqNJ0pLHDyjnw7H+9XrHfFRvew+iPQ1YuyHQg+WjtuqY7Ph87TJCGFQ==", + "version": "2.5.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-2.5.0-next-2024-08-10.tgz", + "integrity": "sha512-rAhnQ9i4lDHAOx+axm8m3NwqGLZFFa4BayDis+bEYp8ufkCgqDjF6v3BeJU3pN7dehUKpg97t8mA0rHGPYFE+A==", "requires": { "@noble/hashes": "^1.3.2", "base58-js": "^1.0.5", @@ -7054,15 +7043,15 @@ } }, "@dfinity/cmc": { - "version": "3.1.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.1.0-next-2024-07-30.tgz", - "integrity": "sha512-d9TtZxI1fuQ0QzogMSTLfyy/WtCTahWgms0R9Htd85z3jZWVJ1+jH85MuG1kfPBQPsLH99izdcM96JlBTqv20w==", + "version": "3.1.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.1.0-next-2024-08-10.tgz", + "integrity": "sha512-msb4dX0wmdfSaTIVred1NkfNOdLonwzQ83RVF1O/Y0U1q++NHFzMAsFc5hS4sig0hCcIgMw/HJ1ZXhNgweBfCw==", "requires": {} }, "@dfinity/gix-components": { - "version": "4.6.0-next-2024-08-06", - "resolved": "https://registry.npmjs.org/@dfinity/gix-components/-/gix-components-4.6.0-next-2024-08-06.tgz", - "integrity": "sha512-oic5/+OqocCRoTBHIcsrrgrpBPDYYSytMcDyYtl1w7nW6ObOxoJig5IM9IBt8w0+J64S67rWnIA+UwQXUK1ufQ==", + "version": "4.6.0-next-2024-08-12", + "resolved": "https://registry.npmjs.org/@dfinity/gix-components/-/gix-components-4.6.0-next-2024-08-12.tgz", + "integrity": "sha512-KjVlUoADfuPKtl4YXfodMINXCmDTxpx5kjmddWwhDc2fxbdoiNdM5V2wkC3QzzYZgxC78QZY+AsMHr4F1JqZTg==", "requires": { "dompurify": "^3.1.6", "html5-qrcode": "^2.3.8", @@ -7070,9 +7059,9 @@ } }, "@dfinity/ic-management": { - "version": "5.1.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.1.0-next-2024-07-30.tgz", - "integrity": "sha512-FvjFqlOMpQ6x58sl9b/B9AEhZ+M8LwZK7Dxe2kKjzroBQ7yFVfuujkogICzk6p/xV6dlMuT/lVNTfFb2JEwd6g==", + "version": "5.1.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.1.0-next-2024-08-10.tgz", + "integrity": "sha512-MVeiGQQTs2cKh1myzwEAZYK0ihyu8tFgToEqeUrP000830cRpkBpAmAde4GzmpeA9aRHatYUrvH1Mtu9L/Gr1A==", "requires": {} }, "@dfinity/identity": { @@ -7086,21 +7075,21 @@ } }, "@dfinity/ledger-icp": { - "version": "2.4.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.4.0-next-2024-07-30.tgz", - "integrity": "sha512-pma0yUdZjyjD+L2lJEtt5YQ3U7OgQ/dGRfyNeECsrZxTXTwJbNR1QrqNzpJQ9pOGaLf7mKcMHb6dUxwdjV63Eg==", + "version": "2.4.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.4.0-next-2024-08-10.tgz", + "integrity": "sha512-aVpUCrvH1/rZ1Us6d42czdSp40/sSXoi6I21lFajDXqOG4Cpqduos/4ICHyAd5rxcWz7DvzmHUGJMgl0FCvJGA==", "requires": {} }, "@dfinity/ledger-icrc": { - "version": "2.4.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.4.0-next-2024-07-30.tgz", - "integrity": "sha512-CJnq4XquTa5eUlOV5eqMJhQWOA2ClUepm9ooTrxse18ka/6r/axb4JEpwK+1Ee7iyNJGa5Ij135bHdc2w2Qw1A==", + "version": "2.4.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.4.0-next-2024-08-10.tgz", + "integrity": "sha512-jK/Xz8uCBBQ6QnQs0vJp17GO7q4VACMyOgvdvo4mDpdhAasEaceqOGeaghlU+E8nYqD9nH/ZPHKYa79Gai0j2g==", "requires": {} }, "@dfinity/nns": { - "version": "5.2.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-5.2.0-next-2024-07-30.tgz", - "integrity": "sha512-VfaLTP15hFiZOh7moTKAgTzCMSMdYCZuP8v0piJ4qLkOIwdlhJMpobf97X2MedL5oaOkTISsU6rKyH5X+RwKmg==", + "version": "5.2.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-5.2.0-next-2024-08-10.tgz", + "integrity": "sha512-WvD9OX9LzrOuExi8HPbGKUUZKkuy7i2rRSNUwcVHsN4pahio6HncTnKLLkgIQ2nf7iGHntCvTtJ00Xz3E6iKzg==", "requires": { "@noble/hashes": "^1.3.2", "randombytes": "^2.1.0" @@ -7115,17 +7104,17 @@ } }, "@dfinity/sns": { - "version": "3.1.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.1.0-next-2024-07-30.tgz", - "integrity": "sha512-lG25m6F8capAVZTdSX07rq3OwbxoNNtaV+jwBrlxX2TO14u0bLrfJ5EiM101wIxfaomF8O/bNJSgrtyPB2Qemg==", + "version": "3.1.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.1.0-next-2024-08-10.tgz", + "integrity": "sha512-D9B/+/IcKEz4TriOoh1q+k3VfWPt6FfwMjFMBlRGHcrOsgut8G5P0TUsK03dX50muLGLRpxwjWMAfF0sI04vjQ==", "requires": { "@noble/hashes": "^1.3.2" } }, "@dfinity/utils": { - "version": "2.4.0-next-2024-07-30", - "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.4.0-next-2024-07-30.tgz", - "integrity": "sha512-e+S6n4JX+ZtF1HzjG32tfVLWM2TMY0Xcbf0sRXmgx4S+DmKr8NA705dq4MmCike/W4RhT2jEljoNIixZwhkV1g==", + "version": "2.4.0-next-2024-08-10", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.4.0-next-2024-08-10.tgz", + "integrity": "sha512-17YrSzkw1E5pIq/yUYKraItdpXhu9XIS5+CX52a8xkChQYkKR4wpQu1r6a9wW53NZq+LOsa+lAl/clJAzTJi7g==", "requires": {} }, "@esbuild/android-arm": { diff --git a/frontend/package.json b/frontend/package.json index cdd248c2b86..92eb55878b4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@dfinity/nns-dapp", - "version": "2.0.85", + "version": "2.0.86", "private": true, "license": "SEE LICENSE IN LICENSE.md", "scripts": { @@ -67,11 +67,11 @@ "@dfinity/candid": "^1.3.0", "@dfinity/ckbtc": "next", "@dfinity/cmc": "next", - "@dfinity/gix-components": "file:../../gix-components", + "@dfinity/gix-components": "next", "@dfinity/ic-management": "next", "@dfinity/identity": "^1.3.0", "@dfinity/ledger-icp": "next", - "@dfinity/ledger-icrc": "file:../../ic-js/packages/ledger-icrc", + "@dfinity/ledger-icrc": "next", "@dfinity/nns": "next", "@dfinity/principal": "^1.3.0", "@dfinity/sns": "next", From 75cfd531d780f3e484852a64d8ddeec7ae0f5b2c Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 13 Aug 2024 11:52:37 +0200 Subject: [PATCH 129/161] cleanup --- .../src/lib/components/universe/UniversePageSummary.svelte | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/lib/components/universe/UniversePageSummary.svelte b/frontend/src/lib/components/universe/UniversePageSummary.svelte index b7545d237f1..f6c91fab03f 100644 --- a/frontend/src/lib/components/universe/UniversePageSummary.svelte +++ b/frontend/src/lib/components/universe/UniversePageSummary.svelte @@ -39,8 +39,4 @@ @include fonts.h3; @include text.truncate; } - - .remove { - color: var(--negative-emphasis); - } From 3e5f876dfa4c44aa26be325ff3c032fcc779a2d6 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Tue, 13 Aug 2024 12:07:05 +0200 Subject: [PATCH 130/161] cleanup --- frontend/src/lib/api/icrc-index.api.ts | 4 ++-- frontend/src/lib/modals/accounts/ImportTokenModal.svelte | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/api/icrc-index.api.ts b/frontend/src/lib/api/icrc-index.api.ts index ce24afc618f..1df05afa861 100644 --- a/frontend/src/lib/api/icrc-index.api.ts +++ b/frontend/src/lib/api/icrc-index.api.ts @@ -55,10 +55,10 @@ export const getLedgerId = async ({ certified?: boolean; }): Promise => { const { - canister: { getLedgerId }, + canister: { ledgerId }, } = await indexCanister({ identity, canisterId: indexCanisterId }); - return getLedgerId({ certified }); + return ledgerId({ certified }); }; const indexCanister = async ({ diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index ac4d88c6c21..06a950e4615 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -11,7 +11,6 @@ import { isNullish, nonNullish } from "@dfinity/utils"; import ImportTokenReview from "$lib/components/accounts/ImportTokenReview.svelte"; import { startBusy, stopBusy } from "$lib/stores/busy.store"; - import { fetchIcrcTokenMetaData } from "$lib/services/icrc-accounts.services"; import { toastsError } from "$lib/stores/toasts.store"; import { importedTokensStore } from "$lib/stores/imported-tokens.store"; import { addImportedToken } from "$lib/services/imported-tokens.services"; @@ -19,6 +18,7 @@ import { goto } from "$app/navigation"; import { createEventDispatcher } from "svelte"; import { validateLedgerIndexPair } from "$lib/services/icrc-index.services"; + import { getIcrcTokenMetaData } from "$lib/services/icrc-accounts.services"; let currentStep: WizardStep | undefined = undefined; const dispatch = createEventDispatcher(); @@ -52,7 +52,7 @@ if (isNullish(ledgerCanisterId)) { return; } - const meta = await fetchIcrcTokenMetaData({ ledgerCanisterId }); + const meta = await getIcrcTokenMetaData({ ledgerCanisterId }); if (isNullish(meta)) { tokenMetaData = undefined; toastsError({ From ffb6bfa67354e07ceef528e5bdf7d3648641bfcf Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 14 Aug 2024 11:31:11 +0200 Subject: [PATCH 131/161] New const IMPORTANT_CK_TOKEN_IDS --- .../constants/important-ck-tokens.constants.ts | 15 +++++++++++++++ frontend/src/lib/utils/tokens-table.utils.ts | 12 ++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 frontend/src/lib/constants/important-ck-tokens.constants.ts diff --git a/frontend/src/lib/constants/important-ck-tokens.constants.ts b/frontend/src/lib/constants/important-ck-tokens.constants.ts new file mode 100644 index 00000000000..edb860f65f3 --- /dev/null +++ b/frontend/src/lib/constants/important-ck-tokens.constants.ts @@ -0,0 +1,15 @@ +import { + CKBTC_UNIVERSE_CANISTER_ID, + CKTESTBTC_UNIVERSE_CANISTER_ID, +} from "$lib/constants/ckbtc-canister-ids.constants"; +import { CKETH_UNIVERSE_CANISTER_ID } from "$lib/constants/cketh-canister-ids.constants"; +import { CKUSDC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckusdc-canister-ids.constants"; + +// Tokens that have significance within the Internet Computer ecosystem. +// The fixed order maps to a descending order in the market cap of the underlying native tokens. +export const IMPORTANT_CK_TOKEN_IDS = [ + CKBTC_UNIVERSE_CANISTER_ID, + CKTESTBTC_UNIVERSE_CANISTER_ID, + CKETH_UNIVERSE_CANISTER_ID, + CKUSDC_UNIVERSE_CANISTER_ID, +]; diff --git a/frontend/src/lib/utils/tokens-table.utils.ts b/frontend/src/lib/utils/tokens-table.utils.ts index ce809e79fec..338ca03bdc0 100644 --- a/frontend/src/lib/utils/tokens-table.utils.ts +++ b/frontend/src/lib/utils/tokens-table.utils.ts @@ -1,7 +1,5 @@ import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; -import { CKBTC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckbtc-canister-ids.constants"; -import { CKETH_UNIVERSE_CANISTER_ID } from "$lib/constants/cketh-canister-ids.constants"; -import { CKUSDC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckusdc-canister-ids.constants"; +import { IMPORTANT_CK_TOKEN_IDS } from "$lib/constants/important-ck-tokens.constants"; import type { UserToken } from "$lib/types/tokens-page"; import { createAscendingComparator, @@ -31,11 +29,9 @@ export const compareTokensWithBalanceOrImportedFirst = ({ // These tokens should be placed before others (but after ICP) // because they have significance within the Internet Computer ecosystem and deserve to be highlighted. // Where the fixed order maps to a descending order in the market cap of the underlying native tokens. -const ImportantCkTokenIds = [ - CKBTC_UNIVERSE_CANISTER_ID.toText(), - CKETH_UNIVERSE_CANISTER_ID.toText(), - CKUSDC_UNIVERSE_CANISTER_ID.toText(), -] +const ImportantCkTokenIds = IMPORTANT_CK_TOKEN_IDS.map((token) => + token.toText() +) // To place other tokens (which get an index of -1) at the bottom. .reverse(); export const compareTokensByImportance = createDescendingComparator( From 9ce3fe6d37426fe5cfe9028507e922f11771a9c5 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 14 Aug 2024 14:59:53 +0200 Subject: [PATCH 132/161] Import token validation before review --- frontend/src/lib/i18n/en.json | 5 +- .../modals/accounts/ImportTokenModal.svelte | 75 +++++++++++++++---- .../lib/services/icrc-accounts.services.ts | 2 +- .../src/lib/services/icrc-index.services.ts | 2 +- frontend/src/lib/types/i18n.d.ts | 3 + .../src/lib/utils/imported-tokens.utils.ts | 22 ++++++ frontend/src/lib/utils/sns.utils.ts | 16 ++++ 7 files changed, 107 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 54dc8b5dcf7..48afec32a8b 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -845,7 +845,10 @@ "load_imported_tokens": "There was an unexpected issue while loading imported tokens.", "add_imported_token": "There was an unexpected issue while adding new imported token.", "remove_imported_token": "There was an unexpected issue while removing the imported token.", - "too_many": "You can't import more than $limit tokens." + "too_many": "You can't import more than $limit tokens.", + "is_duplication": "You have already imported this token, you can find it in the token list.", + "is_sns": "You cannot import SNS tokens, they are added automatically.", + "is_important": "This token is already in the token list." }, "error__sns": { "undefined_project": "The requested project is invalid or throws an error.", diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index 06a950e4615..66154f6e375 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -17,8 +17,14 @@ import { buildWalletUrl } from "$lib/utils/navigation.utils"; import { goto } from "$app/navigation"; import { createEventDispatcher } from "svelte"; - import { validateLedgerIndexPair } from "$lib/services/icrc-index.services"; + import { matchLedgerIndexPair } from "$lib/services/icrc-index.services"; import { getIcrcTokenMetaData } from "$lib/services/icrc-accounts.services"; + import { + isImportantCkToken, + isUniqueImportedToken, + } from "$lib/utils/imported-tokens.utils"; + import { snsProjectsCommittedStore } from "$lib/derived/sns/sns-projects.derived"; + import { isSnsLedgerCanisterId } from "$lib/utils/sns.utils"; let currentStep: WizardStep | undefined = undefined; const dispatch = createEventDispatcher(); @@ -48,39 +54,78 @@ let indexCanisterId: Principal | undefined; let tokenMetaData: IcrcTokenMetadata | undefined; - const getTokenMetaData = async (): Promise => { - if (isNullish(ledgerCanisterId)) { - return; - } - const meta = await getIcrcTokenMetaData({ ledgerCanisterId }); - if (isNullish(meta)) { - tokenMetaData = undefined; + const getTokenMetaData = async ( + ledgerCanisterId: Principal + ): Promise => { + try { + return await getIcrcTokenMetaData({ ledgerCanisterId }); + } catch (err) { toastsError({ labelKey: "import_token.ledger_canister_loading_error", + err, }); - return; } - return meta; }; + const onUserInput = async () => { if (isNullish(ledgerCanisterId)) return; + // Ledger canister ID validation + if ( + !isUniqueImportedToken({ + ledgerCanisterId, + importedTokens: $importedTokensStore?.importedTokens, + }) + ) { + return toastsError({ + labelKey: "error__imported_tokens.is_duplication", + }); + } + if ( + isSnsLedgerCanisterId({ + ledgerCanisterId, + snsProjects: $snsProjectsCommittedStore, + }) + ) { + return toastsError({ + labelKey: "error__imported_tokens.is_sns", + }); + } + if ( + isImportantCkToken({ + ledgerCanisterId, + }) + ) { + return toastsError({ + labelKey: "error__imported_tokens.is_important", + }); + } + startBusy({ initiator: "import-token-validation", labelKey: "import_token.verifying", }); - tokenMetaData = await getTokenMetaData(); + + tokenMetaData = await getTokenMetaData(ledgerCanisterId); + // No need to validate index canister if tokenMetaData fails to load or no index canister is provided const validOrEmptyIndexCanister = - nonNullish(tokenMetaData) && nonNullish(indexCanisterId) - ? await validateLedgerIndexPair({ ledgerCanisterId, indexCanisterId }) - : true; + nonNullish(tokenMetaData) && + (nonNullish(indexCanisterId) + ? await matchLedgerIndexPair({ ledgerCanisterId, indexCanisterId }) + : true); + console.log( + "validOrEmptyIndexCanister", + validOrEmptyIndexCanister, + tokenMetaData + ); stopBusy("import-token-validation"); - if (nonNullish(tokenMetaData) && validOrEmptyIndexCanister) { + if (validOrEmptyIndexCanister) { next(); } }; + const onUserConfirm = async () => { if ( isNullish(ledgerCanisterId) || diff --git a/frontend/src/lib/services/icrc-accounts.services.ts b/frontend/src/lib/services/icrc-accounts.services.ts index a03066e5ca1..e67acfc6285 100644 --- a/frontend/src/lib/services/icrc-accounts.services.ts +++ b/frontend/src/lib/services/icrc-accounts.services.ts @@ -40,7 +40,7 @@ export const getIcrcTokenMetaData = async ({ ledgerCanisterId, }: { ledgerCanisterId: Principal; -}): Promise => { +}): Promise => { return queryIcrcToken({ identity: getCurrentIdentity(), canisterId: ledgerCanisterId, diff --git a/frontend/src/lib/services/icrc-index.services.ts b/frontend/src/lib/services/icrc-index.services.ts index 12ac29f109c..7ac10df3014 100644 --- a/frontend/src/lib/services/icrc-index.services.ts +++ b/frontend/src/lib/services/icrc-index.services.ts @@ -24,7 +24,7 @@ const getLedgerId = async ({ * This function uses `ledger_id` icrc1 index canister api to check if the indexCanisterId is correctly associated with * the provided ledgerCanisterId. */ -export const validateLedgerIndexPair = async ({ +export const matchLedgerIndexPair = async ({ ledgerCanisterId, indexCanisterId, }: { diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 9e02a1eb394..134ad8d2b5c 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -884,6 +884,9 @@ interface I18nError__imported_tokens { add_imported_token: string; remove_imported_token: string; too_many: string; + is_duplication: string; + is_sns: string; + is_important: string; } interface I18nError__sns { diff --git a/frontend/src/lib/utils/imported-tokens.utils.ts b/frontend/src/lib/utils/imported-tokens.utils.ts index 62c112f861d..c434dde9027 100644 --- a/frontend/src/lib/utils/imported-tokens.utils.ts +++ b/frontend/src/lib/utils/imported-tokens.utils.ts @@ -1,4 +1,5 @@ import type { ImportedToken } from "$lib/canisters/nns-dapp/nns-dapp.types"; +import { IMPORTANT_CK_TOKEN_IDS } from "$lib/constants/important-ck-tokens.constants"; import type { ImportedTokenData } from "$lib/types/imported-tokens"; import type { Principal } from "@dfinity/principal"; import { fromNullable, nonNullish, toNullable } from "@dfinity/utils"; @@ -32,3 +33,24 @@ export const isImportedToken = ({ importedTokens.some( ({ ledgerCanisterId: id }) => id.toText() === ledgerCanisterId.toText() ); + +export const isUniqueImportedToken = ({ + ledgerCanisterId, + importedTokens, +}: { + ledgerCanisterId: Principal; + importedTokens: ImportedTokenData[] | undefined; +}): boolean => + nonNullish(importedTokens) && + importedTokens.every( + ({ ledgerCanisterId: id }) => id.toText() !== ledgerCanisterId.toText() + ); + +export const isImportantCkToken = ({ + ledgerCanisterId, +}: { + ledgerCanisterId: Principal; +}): boolean => + IMPORTANT_CK_TOKEN_IDS.some( + (token) => token.toText() === ledgerCanisterId.toText() + ); diff --git a/frontend/src/lib/utils/sns.utils.ts b/frontend/src/lib/utils/sns.utils.ts index 3d4404f318c..54d8b5b4fe7 100644 --- a/frontend/src/lib/utils/sns.utils.ts +++ b/frontend/src/lib/utils/sns.utils.ts @@ -1,5 +1,6 @@ import { SECONDS_IN_DAY } from "$lib/constants/constants"; import { MIN_VALID_SNS_GENERIC_NERVOUS_SYSTEM_FUNCTION_ID } from "$lib/constants/sns-proposals.constants"; +import type { SnsFullProject } from "$lib/derived/sns/sns-projects.derived"; import type { SnsTicketsStoreData } from "$lib/stores/sns-tickets.store"; import type { TicketStatus } from "$lib/types/sale"; import type { SnsSwapCommitment } from "$lib/types/sns"; @@ -202,3 +203,18 @@ export const isSnsGenericNervousSystemTypeProposal = ({ action, }: SnsProposalData): boolean => action >= MIN_VALID_SNS_GENERIC_NERVOUS_SYSTEM_FUNCTION_ID; + +/** + * Returns true if the ledgerId is one of the SNS projects. + */ +export const isSnsLedgerCanisterId = ({ + ledgerCanisterId, + snsProjects, +}: { + ledgerCanisterId: Principal; + snsProjects: SnsFullProject[]; +}): boolean => + snsProjects.some( + ({ summary }) => + summary.ledgerCanisterId.toText() === ledgerCanisterId.toText() + ); From ad478a6f8c6c4efdd905a228c19c860c8a919d70 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 14 Aug 2024 15:00:16 +0200 Subject: [PATCH 133/161] Do not show import buttons before the imported tokens are loaded --- frontend/src/lib/pages/Tokens.svelte | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index 92e3c231ca5..be95df9adb4 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -9,9 +9,10 @@ import { heightTransition } from "$lib/utils/transition.utils"; import { IconPlus, IconSettings } from "@dfinity/gix-components"; import { Popover } from "@dfinity/gix-components"; - import { TokenAmountV2 } from "@dfinity/utils"; + import { nonNullish, TokenAmountV2 } from "@dfinity/utils"; import { ENABLE_IMPORT_TOKEN } from "$lib/stores/feature-flags.store"; import ImportTokenModal from "$lib/modals/accounts/ImportTokenModal.svelte"; + import { importedTokensStore } from "$lib/stores/imported-tokens.store"; export let userTokensData: UserToken[]; @@ -42,6 +43,9 @@ hideZeroBalancesStore.set("show"); }; + let shownImportTokenButton = false; + $: shownImportTokenButton = nonNullish($importedTokensStore.importedTokens); + let showImportTokenModal = false; // TODO(Import token): After removing ENABLE_IMPORT_TOKEN combine divs ->
@@ -78,13 +82,15 @@
{/if} - + {#if shownImportTokenButton} + + {/if}
{:else if shouldHideZeroBalances}
Date: Wed, 14 Aug 2024 15:02:38 +0200 Subject: [PATCH 134/161] Squashed commit of the following: commit af47a6f888a222712b086122a58f0cc818606467 Author: Max Strasinsky Date: Wed Aug 14 10:00:56 2024 +0200 cleanup: remove redundant wrapper commit 5343c8a75f32f7680404d6060b49479ea37b7d4e Author: Max Strasinsky Date: Fri Aug 2 14:55:09 2024 +0200 Update test environment warning. commit 49f95bee04266cc4ba600794116a62923ffd1245 Author: sa-github-api <138766536+sa-github-api@users.noreply.github.com> Date: Tue Aug 13 18:01:12 2024 +0200 bot: Bump ic-js (#5315) # Motivation We want to pull in the latest changes. # Changes * Ran `npm run upgrade:next` # Tests * CI should pass * The pulled in changes should have been tested before being committed to their repositories. Co-authored-by: gix-bot --- .../warnings/TestEnvironmentWarning.svelte | 12 +++++------- frontend/src/lib/i18n/en.json | 4 +--- frontend/src/lib/types/i18n.d.ts | 4 +--- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/frontend/src/lib/components/warnings/TestEnvironmentWarning.svelte b/frontend/src/lib/components/warnings/TestEnvironmentWarning.svelte index 524633de7d0..c02799c4af8 100644 --- a/frontend/src/lib/components/warnings/TestEnvironmentWarning.svelte +++ b/frontend/src/lib/components/warnings/TestEnvironmentWarning.svelte @@ -24,18 +24,12 @@ {$i18n.warning.test_env_title}
-

{$i18n.warning.test_env_welcome}

-

-

- {$i18n.warning.test_env_request} -

-
-
@@ -53,4 +47,8 @@ justify-content: center; padding: var(--padding-2x) 0 var(--padding); } + + p { + padding: 0 var(--padding); + } diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 48afec32a8b..b1667df19b2 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -126,9 +126,7 @@ }, "warning": { "auth_sign_out": "You have been logged out because your session has expired.", - "test_env_welcome": "Welcome to the Testing Environment.", - "test_env_note": "Please note that you are currently using a test version of NNS-dapp that operates on the Internet Computer mainnet. Although it utilizes real data, it is intended solely for testing purposes.", - "test_env_request": "We kindly remind you that the functionality and availability of this testing dapp may change or even disappear at any time. Therefore, it is crucial to refrain from relying on it for any production or critical activities.", + "test_env_note": "Use with caution! This is not stable software, and it is not securely updated by the Network Nervous System. However, it accesses your real wallets and neurons.

Functionality you see here can change at any time, and this dapp may not always be available.", "test_env_confirm": "I understand and want to continue", "test_env_title": "Warning" }, diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 134ad8d2b5c..5813570b0c3 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -131,9 +131,7 @@ interface I18nError { interface I18nWarning { auth_sign_out: string; - test_env_welcome: string; test_env_note: string; - test_env_request: string; test_env_confirm: string; test_env_title: string; } @@ -179,8 +177,8 @@ interface I18nAccounts { add_account: string; new_account_title: string; new_linked_subtitle: string; - attach_hardware_title: string; attach_hardware_subtitle: string; + attach_hardware_title: string; attach_hardware_name_placeholder: string; attach_hardware_enter_name: string; attach_hardware_description: string; From 4af138aba8b91b886a91ec3306dcdb7d1970798a Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Wed, 14 Aug 2024 15:14:35 +0200 Subject: [PATCH 135/161] Squashed commit of the following: commit 25daef42663924fa9ecc784f33326c8ef3145bc4 Author: Max Strasinsky Date: Tue Aug 13 10:02:03 2024 +0200 Use link to icp ledger canister commit 7ab3b0cf6b192d79dcdc17cb6f99f5fcbae4a437 Author: Max Strasinsky Date: Tue Aug 13 10:01:26 2024 +0200 refactor: get rid of the "more-popup-content" tid commit 1a8d43ca9506390563e79d3df6dbae47e403037a Author: Max Strasinsky Date: Tue Aug 13 09:41:44 2024 +0200 Remove noLabel prop commit d3d32623ecee1289852d51235e2c630d2661e1c7 Author: Max Strasinsky Date: Mon Aug 12 17:40:28 2024 +0200 Comments commit b65f790de7273c08caf4e7461ac6889200eb8a25 Author: Max Strasinsky Date: Mon Aug 12 17:20:26 2024 +0200 test: should display more popup on NnsWallet page commit 82dc8284925d02b3a9a73d0a77d7faff4d38d721 Author: Max Strasinsky Date: Mon Aug 12 16:58:33 2024 +0200 test: LinkToDashboardCanister commit 3b598926f8076ab688e7dbdcad428e9dc0e3e6c3 Author: Max Strasinsky Date: Mon Aug 12 16:27:00 2024 +0200 Display popup on Nns wallet page commit c04b7b520e319a156e5ea1c11ff0bedac4680772 Author: Max Strasinsky Date: Mon Aug 12 16:25:42 2024 +0200 Add "actions" slot to the UniversePageSummary commit 8bbbb3aeb9e76d51ba929a584da998f543e06cbf Author: Max Strasinsky Date: Mon Aug 12 16:22:31 2024 +0200 New LinkToDashboardCanister component --- .../components/accounts/IcrcWalletPage.svelte | 14 +++++++- .../accounts/WalletPageHeader.svelte | 1 + .../tokens/LinkToDashboardCanister.svelte | 27 ++++++++++++++++ .../universe/UniversePageSummary.svelte | 16 +--------- frontend/src/lib/i18n/en.json | 1 - frontend/src/lib/pages/NnsWallet.svelte | 6 ++-- frontend/src/lib/types/i18n.d.ts | 3 +- .../tokens/LinkToDashboardCanister.spec.ts | 32 +++++++++++++++++++ .../src/tests/lib/pages/NnsWallet.spec.ts | 23 +++++++++++++ .../LinkToDashboardCanister.page-object.ts | 24 ++++++++++++++ .../page-objects/NnsWallet.page-object.ts | 13 ++++++++ 11 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 frontend/src/lib/components/tokens/LinkToDashboardCanister.svelte create mode 100644 frontend/src/tests/lib/components/tokens/LinkToDashboardCanister.spec.ts create mode 100644 frontend/src/tests/page-objects/LinkToDashboardCanister.page-object.ts diff --git a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte index 32d3dac7027..0d3038fe3a9 100644 --- a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte +++ b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte @@ -20,7 +20,7 @@ hasAccounts, } from "$lib/utils/accounts.utils"; import { replacePlaceholders } from "$lib/utils/i18n.utils"; - import { IconDots, Island, Popover, Spinner } from "@dfinity/gix-components"; + import {IconDots, Island, Popover, Spinner, Tag} from "@dfinity/gix-components"; import type { Principal } from "@dfinity/principal"; import { TokenAmountV2, isNullish, nonNullish } from "@dfinity/utils"; import type { Writable } from "svelte/store"; @@ -30,6 +30,7 @@ import { removeImportedTokens } from "$lib/services/imported-tokens.services"; import TestIdWrapper from "$lib/components/common/TestIdWrapper.svelte"; import LinkToDashboardCanister from "$lib/components/common/LinkToDashboardCanister.svelte"; + import {isImportedToken as checkImportedToken} from "$lib/utils/imported-tokens.utils"; export let testId: string; export let accountIdentifier: string | undefined | null = undefined; @@ -177,6 +178,12 @@ let moreButton: HTMLButtonElement | undefined; let morePopupVisible = false; + + let isImportedToken = false; + $: isImportedToken = checkImportedToken({ + ledgerCanisterId, + importedTokens: $importedTokensStore.importedTokens, + }); @@ -197,6 +204,11 @@ on:nnsRemove={remove} on:nnsMore={() => console.log("icrc")} > + + {#if isImportedToken} + {$i18n.import_token.imported_token} + {/if} + {#if nonNullish(ledgerCanisterId)} + {/if} +
{/if} @@ -288,4 +299,19 @@ .info-card { background-color: var(--island-card-background); } + + .popover-content { + display: flex; + flex-direction: column; + gap: var(--padding-2x); + } + + .remove-button { + padding: 0; + color: var(--negative-emphasis); + + &:hover { + color: inherit; + } + } From ae1ed05304d53a7c49566d999187792e59c79a0e Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Fri, 16 Aug 2024 06:39:04 +0200 Subject: [PATCH 141/161] style: "more" popup buttons --- .../components/accounts/IcrcWalletPage.svelte | 2 +- .../common/LinkToDashboardCanister.svelte | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte index 08be6eec3d2..25ddc445cc2 100644 --- a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte +++ b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte @@ -311,7 +311,7 @@ color: var(--negative-emphasis); &:hover { - color: inherit; + color: var(--negative-emphasis); } } diff --git a/frontend/src/lib/components/common/LinkToDashboardCanister.svelte b/frontend/src/lib/components/common/LinkToDashboardCanister.svelte index b6112b33864..ce8c7c202d8 100644 --- a/frontend/src/lib/components/common/LinkToDashboardCanister.svelte +++ b/frontend/src/lib/components/common/LinkToDashboardCanister.svelte @@ -28,12 +28,18 @@ From fb9907d8a01ab7ca0cb8242c9cf01f0cee5fcacf Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Fri, 16 Aug 2024 06:47:10 +0200 Subject: [PATCH 142/161] style: Import button redesign --- frontend/src/lib/pages/Tokens.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index be95df9adb4..abc101ed21c 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -7,7 +7,7 @@ import { i18n } from "$lib/stores/i18n"; import type { UserToken } from "$lib/types/tokens-page"; import { heightTransition } from "$lib/utils/transition.utils"; - import { IconPlus, IconSettings } from "@dfinity/gix-components"; + import { IconAddCircle, IconSettings } from "@dfinity/gix-components"; import { Popover } from "@dfinity/gix-components"; import { nonNullish, TokenAmountV2 } from "@dfinity/utils"; import { ENABLE_IMPORT_TOKEN } from "$lib/stores/feature-flags.store"; @@ -85,10 +85,10 @@ {#if shownImportTokenButton} {/if}
From 8ac4efe5b60ddb8f320604e6af5997ad1ff832b5 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Fri, 16 Aug 2024 06:54:46 +0200 Subject: [PATCH 143/161] cleanup --- frontend/src/lib/components/accounts/IcrcWalletPage.svelte | 2 -- frontend/src/lib/modals/accounts/ImportTokenModal.svelte | 5 ----- 2 files changed, 7 deletions(-) diff --git a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte index 25ddc445cc2..3824be9f75d 100644 --- a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte +++ b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte @@ -206,8 +206,6 @@ console.log("icrc")} > {#if isImportedToken} diff --git a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte index 66154f6e375..ff9e2b404ab 100644 --- a/frontend/src/lib/modals/accounts/ImportTokenModal.svelte +++ b/frontend/src/lib/modals/accounts/ImportTokenModal.svelte @@ -113,11 +113,6 @@ (nonNullish(indexCanisterId) ? await matchLedgerIndexPair({ ledgerCanisterId, indexCanisterId }) : true); - console.log( - "validOrEmptyIndexCanister", - validOrEmptyIndexCanister, - tokenMetaData - ); stopBusy("import-token-validation"); From ced1d8b6ce3e2f6cf57c9fc4c4f2be088f9b7ef0 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Fri, 16 Aug 2024 09:12:05 +0200 Subject: [PATCH 144/161] feat: remove imported token confirmation dialog (ImportTokenRemoveConfirmation) --- .../components/accounts/IcrcWalletPage.svelte | 14 +++- .../ImportTokenRemoveConfirmation.svelte | 67 +++++++++++++++++++ frontend/src/lib/i18n/en.json | 4 +- frontend/src/lib/types/i18n.d.ts | 2 + 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte diff --git a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte index 3824be9f75d..461356bd779 100644 --- a/frontend/src/lib/components/accounts/IcrcWalletPage.svelte +++ b/frontend/src/lib/components/accounts/IcrcWalletPage.svelte @@ -23,7 +23,6 @@ import { IconBin, IconDots, - IconOpenInNew, Island, Popover, Spinner, @@ -39,6 +38,7 @@ import TestIdWrapper from "$lib/components/common/TestIdWrapper.svelte"; import LinkToDashboardCanister from "$lib/components/common/LinkToDashboardCanister.svelte"; import { isImportedToken as checkImportedToken } from "$lib/utils/imported-tokens.utils"; + import ImportTokenRemoveConfirmation from "$lib/components/accounts/ImportTokenRemoveConfirmation.svelte"; export let testId: string; export let accountIdentifier: string | undefined | null = undefined; @@ -189,6 +189,8 @@ ledgerCanisterId, importedTokens: $importedTokensStore.importedTokens, }); + + let removeImportedTokenConfirmtionVisible = false; @@ -276,7 +278,7 @@
{/if} + + {#if removeImportedTokenConfirmtionVisible} + (removeImportedTokenConfirmtionVisible = false)} + on:nnsConfirm={remove} + /> + {/if} diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index e5ad3c8d06c..71051528fd8 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1055,6 +1055,8 @@ "remove_imported_token_success": "The token has been successfully removed!", "view_in_dashboard": "View in Dashboard", "link_to_dashboard": "https://dashboard.internetcomputer.org/canister/$canisterId", - "link_to_canister": "https://dashboard.internetcomputer.org/canister/$canisterId" + "link_to_canister": "https://dashboard.internetcomputer.org/canister/$canisterId", + "remove_confirmation_title": "Remove Token", + "remove_confirmation_description": "Are you sure you want to remove this token from your account?

Tokens you hold in your account will not be lost, and you can add the token back in the future." } } diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index e3c2d9b5c1e..238c98c1c66 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1115,6 +1115,8 @@ interface I18nImport_token { view_in_dashboard: string; link_to_dashboard: string; link_to_canister: string; + remove_confirmation_title: string; + remove_confirmation_description: string; } interface I18nNeuron_state { From 10cf9ce7798b9dfd8eac000951d2376bf7b9e3e8 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Fri, 16 Aug 2024 09:40:20 +0200 Subject: [PATCH 145/161] Formatting --- .../ImportTokenRemoveConfirmation.svelte | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte b/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte index 152287648d1..b1a7e0db1cf 100644 --- a/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte @@ -6,12 +6,14 @@ import type { Principal } from "@dfinity/principal"; import type { Universe } from "$lib/types/universe"; import { nonNullish } from "@dfinity/utils"; - import {selectableUniversesStore} from "$lib/derived/selectable-universes.derived"; + import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived"; export let ledgerCanisterId: Principal; let universe: Universe | undefined; - $: universe = $selectableUniversesStore.find(({canisterId})=> canisterId === ledgerCanisterId.toText()); + $: universe = $selectableUniversesStore.find( + ({ canisterId }) => canisterId === ledgerCanisterId.toText() + ); const dispatch = createEventDispatcher(); @@ -37,23 +39,23 @@

-
- +
+ - -
+ +
From 1b51c1c1a96d34837531344c811c9fbb9d58b829 Mon Sep 17 00:00:00 2001 From: Max Strasinsky Date: Fri, 23 Aug 2024 14:16:42 +0200 Subject: [PATCH 158/161] Cleanup RemoveConfirmation component --- .../accounts/ImportTokenRemoveConfirmation.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte b/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte index 2279eaa09c0..7bb1d252bb0 100644 --- a/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte +++ b/frontend/src/lib/components/accounts/ImportTokenRemoveConfirmation.svelte @@ -10,18 +10,18 @@ export let ledgerCanisterId: Principal; + const dispatch = createEventDispatcher(); + let universe: Universe | undefined; $: universe = $selectableUniversesStore.find( ({ canisterId }) => canisterId === ledgerCanisterId.toText() ); - - const dispatch = createEventDispatcher(); @@ -31,9 +31,9 @@
{#if nonNullish(universe)}{/if} - {$i18n.import_token.imported_token} + {$i18n.import_token.imported_token}
-

+

- - -
+

{$i18n.import_token.remove_confirmation_description_1}

+

{$i18n.import_token.remove_confirmation_description_2}

-
+