Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): optional index canister #3334

Merged
merged 44 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b4f39a8
feat(frontend): ic token zod
peterpeterparker Nov 5, 2024
4de89dd
feat: validate data
peterpeterparker Nov 5, 2024
e9caffd
feat(frontend): optional index canister
peterpeterparker Nov 5, 2024
476eef1
chore: merge main
peterpeterparker Nov 5, 2024
461dcfd
feat: use principal schema
peterpeterparker Nov 5, 2024
3359445
test: proper canister id
peterpeterparker Nov 5, 2024
ff5c7a7
chore: merge main
peterpeterparker Nov 5, 2024
8394355
Merge branch 'feat/ic-token-zod' into feat/optional-index-canister
peterpeterparker Nov 5, 2024
86a35cd
feat: IcCanistersStrictSchema
peterpeterparker Nov 5, 2024
327d1b9
chore: merge main
peterpeterparker Nov 5, 2024
320e995
chore: merge main
peterpeterparker Nov 5, 2024
f13e646
feat: assert index canister id
peterpeterparker Nov 5, 2024
2b5cb48
🤖 Apply formatting changes
github-actions[bot] Nov 5, 2024
e44d2a0
feat: isNot
peterpeterparker Nov 5, 2024
8bf63b5
Merge remote-tracking branch 'origin/feat/optional-index-canister' in…
peterpeterparker Nov 5, 2024
1058370
feat: isNot
peterpeterparker Nov 5, 2024
f33f911
feat: tmp workaround
peterpeterparker Nov 5, 2024
3c6e04f
test: validation index
peterpeterparker Nov 5, 2024
a2eaed5
chore: lint
peterpeterparker Nov 5, 2024
8c70434
chore: merge main
peterpeterparker Nov 5, 2024
6c6f9f0
chore: merge main
peterpeterparker Nov 5, 2024
9b2ba90
feat: redo after merge
peterpeterparker Nov 5, 2024
1ff7ce7
feat: redo after merge
peterpeterparker Nov 5, 2024
1ffba52
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 5, 2024
575530a
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 6, 2024
eb79808
chore: merge main
peterpeterparker Nov 6, 2024
c760934
chore: merge main
peterpeterparker Nov 6, 2024
88a22e6
test: can call without index canister
peterpeterparker Nov 6, 2024
9854279
test: without index canister
peterpeterparker Nov 6, 2024
fd0e891
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 6, 2024
ada54c2
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 7, 2024
0664fc6
feat: add todos
peterpeterparker Nov 7, 2024
066e7e3
docs: jira number
peterpeterparker Nov 7, 2024
4a235f0
chore: merge main
peterpeterparker Nov 7, 2024
332873a
🤖 Apply formatting changes
github-actions[bot] Nov 7, 2024
c2da13a
test: update for optional index canister id
peterpeterparker Nov 7, 2024
e276f7a
Merge remote-tracking branch 'origin/feat/optional-index-canister' in…
peterpeterparker Nov 7, 2024
ebea89a
feat: invert
peterpeterparker Nov 7, 2024
0bb9690
refactor: rename local mock variable
peterpeterparker Nov 7, 2024
91307c9
chore: merge main
peterpeterparker Nov 7, 2024
9854ef0
🤖 Apply formatting changes
github-actions[bot] Nov 7, 2024
45941a1
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 8, 2024
02f6ec2
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 8, 2024
7a29d06
Merge branch 'main' into feat/optional-index-canister
peterpeterparker Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { icTransactions } from '$icp/derived/ic-transactions.derived';
import { loadNextTransactions } from '$icp/services/ic-transactions.services';
import type { IcTransactionUi } from '$icp/types/ic-transaction';
import { isNotIcToken, isNotIcTokenCanistersStrict } from '$icp/validation/ic-token.validation';
import TransactionsPlaceholder from '$lib/components/transactions/TransactionsPlaceholder.svelte';
import Header from '$lib/components/ui/Header.svelte';
import { WALLET_PAGINATION } from '$lib/constants/app.constants';
Expand Down Expand Up @@ -69,6 +70,12 @@
return;
}

if (isNotIcToken($tokenAsIcToken) || isNotIcTokenCanistersStrict($tokenAsIcToken)) {
// On one hand, we assume that the parent component does not mount this component if no transactions can be fetched; on the other hand, we want to avoid displaying an error toast that could potentially appear multiple times.
// Therefore, we do not particularly display a visual error. In any case, we cannot load transactions without an Index canister.
return;
}

await loadNextTransactions({
owner: $authIdentity.getPrincipal(),
identity: $authIdentity,
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>({
AntonioVentilii marked this conversation as resolved.
Show resolved Hide resolved
defaultToken: {
ledgerCanisterId,
indexCanisterId,
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/src/icp/schema/ic-token.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export const IcAppMetadataSchema = z.object({

export const IcCanistersSchema = z.object({
ledgerCanisterId: CanisterIdTextSchema,
// TODO: Make canister .optional()
indexCanisterId: CanisterIdTextSchema
indexCanisterId: CanisterIdTextSchema.optional()
});

export const IcCanistersStrictSchema = IcCanistersSchema.extend({
Expand Down
62 changes: 40 additions & 22 deletions src/frontend/src/icp/services/ic-add-custom-tokens.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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-token';
import { mapIcrcToken } from '$icp/utils/icrc.utils';
import { i18n } from '$lib/stores/i18n.store';
import { toastsError } from '$lib/stores/toasts.store';
import type { OptionIdentity } from '$lib/types/identity';
import type { Identity } from '@dfinity/agent';
import { assertNonNullish, isNullish } from '@dfinity/utils';
import { assertNonNullish, isNullish, nonNullish } from '@dfinity/utils';
import { get } from 'svelte/store';

export interface ValidateTokenData {
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 } = nonNullish(indexCanisterId)
? await assertIndexLedgerId({
identity,
...canisterIds,
indexCanisterId
})
: { valid: true };

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
6 changes: 3 additions & 3 deletions src/frontend/src/icp/services/ic-transactions.services.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getTransactions as getTransactionsIcp } from '$icp/api/icp-index.api';
import { getTransactions as getTransactionsIcrc } from '$icp/api/icrc-index-ng.api';
import { icTransactionsStore } from '$icp/stores/ic-transactions.store';
import type { IcToken } from '$icp/types/ic-token';
import type { IcCanistersStrict, IcToken } from '$icp/types/ic-token';
import type { IcTransaction } from '$icp/types/ic-transaction';
import { mapIcTransaction } from '$icp/utils/ic-transactions.utils';
import { mapTransactionIcpToSelf } from '$icp/utils/icp-transactions.utils';
Expand All @@ -23,7 +23,7 @@ const getTransactions = async ({
identity: OptionIdentity;
start?: bigint;
maxResults?: bigint;
token: IcToken;
token: IcToken & IcCanistersStrict;
}): Promise<IcTransaction[]> => {
if (standard === 'icrc') {
const { transactions } = await getTransactionsIcrc({
Expand All @@ -49,7 +49,7 @@ export const loadNextTransactions = ({
identity: OptionIdentity;
start?: bigint;
maxResults?: bigint;
token: IcToken;
token: IcToken & IcCanistersStrict;
AntonioVentilii marked this conversation as resolved.
Show resolved Hide resolved
signalEnd: () => void;
}): Promise<void> =>
queryAndUpdate<IcTransaction[]>({
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/icp/services/icrc.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const loadCustomIcrcTokensData = async ({

const indexCanisterId = fromNullable(index_id);

// TODO(OISY-296): remove isNullish(indexCanisterId) when support for reading balance and no index is fully implemented
AntonioVentilii marked this conversation as resolved.
Show resolved Hide resolved
// Index canister ID currently mandatory in Oisy's frontend
if (isNullish(indexCanisterId)) {
return undefined;
Expand Down
7 changes: 6 additions & 1 deletion src/frontend/src/icp/workers/icrc-wallet.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ const getTransactions = ({
}: SchedulerJobParams<PostMessageDataRequestIcrc>): Promise<IcrcIndexNgGetTransactions> => {
assertNonNullish(data, 'No data - indexCanisterId - provided to fetch transactions.');

// TODO(OISY-296): This is not clean. If the index canister ID is not provided we should not even land here.
const { indexCanisterId } = data;
assertNonNullish(indexCanisterId);

return getTransactionsApi({
identity,
certified,
owner: identity.getPrincipal(),
// We query tip to discover the new transactions
start: undefined,
...data
...data,
indexCanisterId
peterpeterparker marked this conversation as resolved.
Show resolved Hide resolved
});
};

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
Loading
Loading