diff --git a/src/frontend/src/icp-eth/services/custom-token.services.ts b/src/frontend/src/icp-eth/services/custom-token.services.ts index 8000855898..54fac35c6d 100644 --- a/src/frontend/src/icp-eth/services/custom-token.services.ts +++ b/src/frontend/src/icp-eth/services/custom-token.services.ts @@ -10,7 +10,7 @@ import type { OptionIdentity } from '$lib/types/identity'; import type { Token } from '$lib/types/token'; import type { Identity } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; -import { isNullish, toNullable } from '@dfinity/utils'; +import { isNullish, nonNullish, toNullable } from '@dfinity/utils'; import { get } from 'svelte/store'; const assertErc20SendTokenData = (sendToken: Erc20Token): AutoLoadTokenResult | undefined => { @@ -72,7 +72,9 @@ export const toCustomToken = ({ token: { Icrc: { ledger_id: Principal.fromText(ledgerCanisterId), - index_id: toNullable(Principal.fromText(indexCanisterId)) + index_id: toNullable( + nonNullish(indexCanisterId) ? Principal.fromText(indexCanisterId) : undefined + ) } } }); diff --git a/src/frontend/src/icp/api/icrc-ledger.api.ts b/src/frontend/src/icp/api/icrc-ledger.api.ts index 97ffd74ba8..1766c49ce8 100644 --- a/src/frontend/src/icp/api/icrc-ledger.api.ts +++ b/src/frontend/src/icp/api/icrc-ledger.api.ts @@ -1,4 +1,5 @@ import { nowInBigIntNanoSeconds } from '$icp/utils/date.utils'; +import { getIcrcAccount } from '$icp/utils/icrc-account.utils'; import { getAgent } from '$lib/actors/agents.ic'; import type { CanisterIdText } from '$lib/types/canister'; import type { OptionIdentity } from '$lib/types/identity'; @@ -10,6 +11,7 @@ import { type IcrcSubaccount, type IcrcTokenMetadataResponse } from '@dfinity/ledger-icrc'; +import type { Tokens } from '@dfinity/ledger-icrc/dist/candid/icrc_ledger'; import { Principal } from '@dfinity/principal'; import { assertNonNullish, toNullable, type QueryParams } from '@dfinity/utils'; @@ -28,6 +30,23 @@ export const metadata = async ({ return metadata({ certified }); }; +export const balance = async ({ + certified = true, + owner, + identity, + ...rest +}: { + owner: Principal; + identity: OptionIdentity; + ledgerCanisterId: CanisterIdText; +} & QueryParams): Promise => { + assertNonNullish(identity); + + const { balance } = await ledgerCanister({ identity, ...rest }); + + return balance({ certified, ...getIcrcAccount(owner) }); +}; + export const transfer = async ({ identity, to, diff --git a/src/frontend/src/icp/derived/icrc.derived.ts b/src/frontend/src/icp/derived/icrc.derived.ts index 335e91cb79..69728f4a29 100644 --- a/src/frontend/src/icp/derived/icrc.derived.ts +++ b/src/frontend/src/icp/derived/icrc.derived.ts @@ -71,7 +71,7 @@ const icrcDefaultTokensToggleable: Readable = derived( userLedgerCanisterId === ledgerCanisterId && userIndexCanisterId === indexCanisterId ); - return mapDefaultTokenToToggleable({ + return mapDefaultTokenToToggleable({ defaultToken: { ledgerCanisterId, indexCanisterId, diff --git a/src/frontend/src/icp/services/ic-add-custom-tokens.service.ts b/src/frontend/src/icp/services/ic-add-custom-tokens.service.ts index d8da341e43..dc5bb32bee 100644 --- a/src/frontend/src/icp/services/ic-add-custom-tokens.service.ts +++ b/src/frontend/src/icp/services/ic-add-custom-tokens.service.ts @@ -1,5 +1,5 @@ import { getLedgerId, getTransactions as getTransactionsIcrc } from '$icp/api/icrc-index-ng.api'; -import { metadata } from '$icp/api/icrc-ledger.api'; +import { balance, metadata } from '$icp/api/icrc-ledger.api'; import type { IcCanisters, IcToken, IcTokenWithoutId } from '$icp/types/ic'; import { mapIcrcToken } from '$icp/utils/icrc.utils'; import { i18n } from '$lib/stores/i18n.store'; @@ -35,13 +35,6 @@ export const loadAndAssertAddCustomToken = async ({ return { result: 'error' }; } - if (isNullish(indexCanisterId)) { - toastsError({ - msg: { text: get(i18n).tokens.import.error.missing_index_id } - }); - return { result: 'error' }; - } - const canisterIds = { ledgerCanisterId, indexCanisterId }; const { alreadyAvailable } = assertAlreadyAvailable({ @@ -53,22 +46,26 @@ export const loadAndAssertAddCustomToken = async ({ return { result: 'error' }; } - const { valid } = await assertLedgerId({ - identity, - ...canisterIds - }); + const { valid } = isNullish(indexCanisterId) + ? { valid: true } + : await assertIndexLedgerId({ + identity, + ...canisterIds, + indexCanisterId + }); if (!valid) { return { result: 'error' }; } try { + const params = { identity, ...canisterIds }; + const [token, balance] = await Promise.all([ - loadMetadata({ - identity, - ...canisterIds - }), - loadBalance({ identity, ...canisterIds }) + loadMetadata(params), + ...(isNullish(indexCanisterId) + ? [loadLedgerBalance(params)] + : [loadIndexBalance({ ...params, indexCanisterId })]) ]); if (isNullish(token)) { @@ -157,10 +154,31 @@ const loadMetadata = async ({ } }; -const loadBalance = async ({ +const loadLedgerBalance = async ({ + identity, + ledgerCanisterId +}: IcCanisters & { identity: Identity }): Promise => { + try { + return await balance({ + ledgerCanisterId, + identity, + owner: identity.getPrincipal(), + certified: true + }); + } catch (err: unknown) { + toastsError({ + msg: { text: get(i18n).tokens.import.error.unexpected_ledger }, + err + }); + + throw err; + } +}; + +const loadIndexBalance = async ({ identity, indexCanisterId -}: Pick & { identity: Identity }): Promise => { +}: Required> & { identity: Identity }): Promise => { try { const { balance } = await getTransactionsIcrc({ indexCanisterId, @@ -181,11 +199,11 @@ const loadBalance = async ({ } }; -const assertLedgerId = async ({ +const assertIndexLedgerId = async ({ identity, indexCanisterId, ledgerCanisterId -}: IcCanisters & { identity: Identity }): Promise<{ valid: boolean }> => { +}: Required & { identity: Identity }): Promise<{ valid: boolean }> => { try { const ledgerId = await getLedgerId({ indexCanisterId, diff --git a/src/frontend/src/icp/validation/ic-token.validation.ts b/src/frontend/src/icp/validation/ic-token.validation.ts index 43a38ce3f3..3c86af397c 100644 --- a/src/frontend/src/icp/validation/ic-token.validation.ts +++ b/src/frontend/src/icp/validation/ic-token.validation.ts @@ -16,7 +16,7 @@ export const IcAppMetadataSchema = z.object({ export const IcCanistersSchema = z.object({ ledgerCanisterId: CanisterIdTextSchema, - indexCanisterId: CanisterIdTextSchema + indexCanisterId: CanisterIdTextSchema.optional() }); export const IcCkLinkedAssetsSchema = z.object({ diff --git a/src/frontend/src/lib/i18n/en.json b/src/frontend/src/lib/i18n/en.json index a8c8dc29f0..f3d89ef332 100644 --- a/src/frontend/src/lib/i18n/en.json +++ b/src/frontend/src/lib/i18n/en.json @@ -435,6 +435,7 @@ "error": { "loading_metadata": "Error while loading the metadata of the token.", "no_metadata": "No metadata for the token is provided. This is unexpected.", + "unexpected_ledger": "Something went wrong while validating the Ledger canister.", "unexpected_index": "Something went wrong while validating the Index canister.", "unexpected_index_ledger": "Something went wrong while loading the Ledger ID related to the Index canister.", "invalid_ledger_id": "The Ledger ID is not related to the Index canister.", diff --git a/src/frontend/src/lib/types/i18n.d.ts b/src/frontend/src/lib/types/i18n.d.ts index 01e4743a3e..802356e3b1 100644 --- a/src/frontend/src/lib/types/i18n.d.ts +++ b/src/frontend/src/lib/types/i18n.d.ts @@ -396,6 +396,7 @@ interface I18nTokens { error: { loading_metadata: string; no_metadata: string; + unexpected_ledger: string; unexpected_index: string; unexpected_index_ledger: string; invalid_ledger_id: string;