Skip to content

Commit

Permalink
feat(frontend): optional index canister
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpeterparker committed Nov 5, 2024
1 parent 4de89dd commit e9caffd
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 25 deletions.
6 changes: 4 additions & 2 deletions src/frontend/src/icp-eth/services/custom-token.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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
)
}
}
});
Expand Down
19 changes: 19 additions & 0 deletions src/frontend/src/icp/api/icrc-ledger.api.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';

Expand All @@ -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<Tokens> => {
assertNonNullish(identity);

const { balance } = await ledgerCanister({ identity, ...rest });

return balance({ certified, ...getIcrcAccount(owner) });
};

export const transfer = async ({
identity,
to,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/icp/derived/icrc.derived.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const icrcDefaultTokensToggleable: Readable<IcTokenToggleable[]> = derived(
userLedgerCanisterId === ledgerCanisterId && userIndexCanisterId === indexCanisterId
);

return mapDefaultTokenToToggleable({
return mapDefaultTokenToToggleable<IcToken>({
defaultToken: {
ledgerCanisterId,
indexCanisterId,
Expand Down
60 changes: 39 additions & 21 deletions src/frontend/src/icp/services/ic-add-custom-tokens.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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({
Expand All @@ -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)) {
Expand Down Expand Up @@ -157,10 +154,31 @@ const loadMetadata = async ({
}
};

const loadBalance = async ({
const loadLedgerBalance = async ({
identity,
ledgerCanisterId
}: IcCanisters & { identity: Identity }): Promise<bigint> => {
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<IcCanisters, 'indexCanisterId'> & { identity: Identity }): Promise<bigint> => {
}: Required<Pick<IcCanisters, 'indexCanisterId'>> & { identity: Identity }): Promise<bigint> => {
try {
const { balance } = await getTransactionsIcrc({
indexCanisterId,
Expand All @@ -181,11 +199,11 @@ const loadBalance = async ({
}
};

const assertLedgerId = async ({
const assertIndexLedgerId = async ({
identity,
indexCanisterId,
ledgerCanisterId
}: IcCanisters & { identity: Identity }): Promise<{ valid: boolean }> => {
}: Required<IcCanisters> & { identity: Identity }): Promise<{ valid: boolean }> => {
try {
const ledgerId = await getLedgerId({
indexCanisterId,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/icp/validation/ic-token.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/lib/types/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit e9caffd

Please sign in to comment.