diff --git a/src/frontend/src/btc/components/convert/BtcConvertFeeTotal.svelte b/src/frontend/src/btc/components/convert/BtcConvertFeeTotal.svelte
index c96d9ca446..7ef607785b 100644
--- a/src/frontend/src/btc/components/convert/BtcConvertFeeTotal.svelte
+++ b/src/frontend/src/btc/components/convert/BtcConvertFeeTotal.svelte
@@ -1,4 +1,5 @@
- import { isNullish } from '@dfinity/utils';
import { getContext } from 'svelte';
import { BTC_CONVERT_FEE } from '$btc/constants/btc.constants';
import { UTXOS_FEE_CONTEXT_KEY, type UtxosFeeContext } from '$btc/stores/utxos-fee.store';
@@ -7,9 +6,6 @@
import ConvertFee from '$lib/components/convert/ConvertFee.svelte';
import { CONVERT_CONTEXT_KEY, type ConvertContext } from '$lib/stores/convert.store';
import { i18n } from '$lib/stores/i18n.store';
- import type { OptionAmount } from '$lib/types/send';
-
- export let sendAmount: OptionAmount;
const { sourceToken, sourceTokenExchangeRate, destinationToken } =
getContext(CONVERT_CONTEXT_KEY);
@@ -20,10 +16,7 @@
$: kytFee = $ckBtcMinterInfoStore?.[$destinationToken.id]?.data.kyt_fee;
let satoshisFee: bigint | undefined;
- $: satoshisFee =
- isNullish(sendAmount) || Number(sendAmount) === 0
- ? 0n
- : $storeUtxosFeeData?.utxosFee?.feeSatoshis;
+ $: satoshisFee = $storeUtxosFeeData?.utxosFee?.feeSatoshis;
;
$: hasPendingTransactionsStore = initPendingSentTransactionsStatus(source);
+ let utxosFee: UtxosFee | undefined;
+ $: utxosFee = nonNullish(sendAmount) ? $storeUtxosFeeData?.utxosFee : undefined;
+
let invalid: boolean;
$: invalid =
insufficientFunds ||
@@ -57,16 +61,13 @@
{:else if nonNullish($hasPendingTransactionsStore)}
-
+
{/if}
-
+
diff --git a/src/frontend/src/btc/components/convert/BtcConvertReview.svelte b/src/frontend/src/btc/components/convert/BtcConvertReview.svelte
index b26c7ff0b2..6ea2603868 100644
--- a/src/frontend/src/btc/components/convert/BtcConvertReview.svelte
+++ b/src/frontend/src/btc/components/convert/BtcConvertReview.svelte
@@ -19,7 +19,7 @@
-
+
diff --git a/src/frontend/src/icp/components/transactions/IcTransactions.svelte b/src/frontend/src/icp/components/transactions/IcTransactions.svelte
index 1b24639c30..da92e37f22 100644
--- a/src/frontend/src/icp/components/transactions/IcTransactions.svelte
+++ b/src/frontend/src/icp/components/transactions/IcTransactions.svelte
@@ -24,7 +24,7 @@
import { icTransactions } from '$icp/derived/ic-transactions.derived';
import { icTransactionsStore } from '$icp/stores/ic-transactions.store';
import type { IcTransactionUi } from '$icp/types/ic-transaction';
- import { isIcTokenCanistersStrict } from '$icp/validation/ic-token.validation';
+ import { hasIndexCanister } from '$icp/validation/ic-token.validation';
import TransactionsPlaceholder from '$lib/components/transactions/TransactionsPlaceholder.svelte';
import Header from '$lib/components/ui/Header.svelte';
import { modalIcToken, modalIcTransaction } from '$lib/derived/modal.derived';
@@ -54,9 +54,6 @@
let noTransactions = false;
$: noTransactions = nonNullish($token) && $icTransactionsStore?.[$token.id] === null;
-
- let hasIndexCanister = false;
- $: hasIndexCanister = nonNullish($tokenAsIcToken) && isIcTokenCanistersStrict($tokenAsIcToken);
@@ -86,7 +83,9 @@
{/if}
{#if noTransactions}
-
+
{:else if $icTransactions.length === 0}
{/if}
diff --git a/src/frontend/src/icp/validation/ic-token.validation.ts b/src/frontend/src/icp/validation/ic-token.validation.ts
index 051d186bba..5135252378 100644
--- a/src/frontend/src/icp/validation/ic-token.validation.ts
+++ b/src/frontend/src/icp/validation/ic-token.validation.ts
@@ -5,6 +5,7 @@ import {
} from '$icp/schema/ic-token.schema';
import type { IcCanistersStrict, IcCkToken, IcToken } from '$icp/types/ic-token';
import type { Token } from '$lib/types/token';
+import { nonNullish } from '@dfinity/utils';
export const isIcToken = (token: Token): token is IcToken => {
const { success } = IcTokenSchema.safeParse(token);
@@ -29,3 +30,8 @@ export const isIcCkToken = (token: Token): token is IcCkToken => {
export const isNotIcCkToken = (token: Token): token is Exclude =>
!isIcCkToken(token);
+
+export const hasIndexCanister = (token: IcToken): boolean =>
+ nonNullish(token) && isIcTokenCanistersStrict(token);
+
+export const hasNoIndexCanister = (token: IcToken): boolean => !hasIndexCanister(token);
diff --git a/src/frontend/src/lib/components/core/SignOut.svelte b/src/frontend/src/lib/components/core/SignOut.svelte
index 1ac2b0f3da..dd7fe07514 100644
--- a/src/frontend/src/lib/components/core/SignOut.svelte
+++ b/src/frontend/src/lib/components/core/SignOut.svelte
@@ -9,7 +9,7 @@
const logout = async () => {
dispatch('icLogoutTriggered');
- await signOut();
+ await signOut({ resetUrl: true });
};
diff --git a/src/frontend/src/lib/components/loaders/Loader.svelte b/src/frontend/src/lib/components/loaders/Loader.svelte
index 16319fa970..e5efe2691c 100644
--- a/src/frontend/src/lib/components/loaders/Loader.svelte
+++ b/src/frontend/src/lib/components/loaders/Loader.svelte
@@ -116,7 +116,7 @@
);
if (!addressSuccess) {
- await signOut();
+ await signOut({});
return;
}
diff --git a/src/frontend/src/lib/components/transactions/AllTransactions.svelte b/src/frontend/src/lib/components/transactions/AllTransactions.svelte
index 6c5452c618..bca6e85847 100644
--- a/src/frontend/src/lib/components/transactions/AllTransactions.svelte
+++ b/src/frontend/src/lib/components/transactions/AllTransactions.svelte
@@ -1,10 +1,29 @@
@@ -16,6 +35,14 @@
{/if}
+ {#if notEmptyString(tokenListWithoutCanister)}
+
+ {replacePlaceholders($i18n.activity.warning.no_index_canister, {
+ $token_list: tokenListWithoutCanister
+ })}
+
+ {/if}
+
{$i18n.activity.info.btc_transactions}
diff --git a/src/frontend/src/lib/i18n/en.json b/src/frontend/src/lib/i18n/en.json
index 4100ac64ab..72243f83c4 100644
--- a/src/frontend/src/lib/i18n/en.json
+++ b/src/frontend/src/lib/i18n/en.json
@@ -828,6 +828,9 @@
},
"info": {
"btc_transactions": "BTC transaction information is obtained from central third parties and should be independently verified."
+ },
+ "warning": {
+ "no_index_canister": "Transaction for $token_list can not be displayed. No Index canister."
}
}
}
diff --git a/src/frontend/src/lib/services/auth.services.ts b/src/frontend/src/lib/services/auth.services.ts
index 3d3fb594f0..7309d0c054 100644
--- a/src/frontend/src/lib/services/auth.services.ts
+++ b/src/frontend/src/lib/services/auth.services.ts
@@ -11,6 +11,7 @@ import { i18n } from '$lib/stores/i18n.store';
import { testnetsStore } from '$lib/stores/settings.store';
import { toastsClean, toastsError, toastsShow } from '$lib/stores/toasts.store';
import type { ToastMsg } from '$lib/types/toast';
+import { gotoReplaceRoot } from '$lib/utils/nav.utils';
import { replaceHistory } from '$lib/utils/route.utils';
import type { ToastLevel } from '@dfinity/gix-components';
import type { Principal } from '@dfinity/principal';
@@ -58,7 +59,8 @@ export const signIn = async (
}
};
-export const signOut = (): Promise => logout({});
+export const signOut = ({ resetUrl = false }: { resetUrl?: boolean }): Promise =>
+ logout({ resetUrl });
export const errorSignOut = (text: string): Promise =>
logout({
@@ -115,10 +117,12 @@ const clearTestnetsOption = async () => {
const logout = async ({
msg = undefined,
- clearStorages = true
+ clearStorages = true,
+ resetUrl = false
}: {
msg?: ToastMsg;
clearStorages?: boolean;
+ resetUrl?: boolean;
}) => {
// To mask not operational UI (a side effect of sometimes slow JS loading after window.reload because of service worker and no cache).
busy.start();
@@ -133,6 +137,10 @@ const logout = async ({
appendMsgToUrl(msg);
}
+ if (resetUrl) {
+ await gotoReplaceRoot();
+ }
+
// Auth: Delegation and identity are cleared from indexedDB by agent-js so, we do not need to clear these
// Preferences: We do not clear local storage as well. It contains anonymous information such as the selected theme.
diff --git a/src/frontend/src/lib/types/i18n.d.ts b/src/frontend/src/lib/types/i18n.d.ts
index 10f3837703..bda66a6b5d 100644
--- a/src/frontend/src/lib/types/i18n.d.ts
+++ b/src/frontend/src/lib/types/i18n.d.ts
@@ -702,6 +702,7 @@ interface I18nLicense_agreement {
interface I18nActivity {
text: { title: string };
info: { btc_transactions: string };
+ warning: { no_index_canister: string };
}
interface I18n {
diff --git a/src/frontend/src/lib/utils/info.utils.ts b/src/frontend/src/lib/utils/info.utils.ts
index f2a79f8893..3c9d569103 100644
--- a/src/frontend/src/lib/utils/info.utils.ts
+++ b/src/frontend/src/lib/utils/info.utils.ts
@@ -4,7 +4,8 @@ export type HideInfoKey =
| 'oisy_ic_hide_bitcoin_info'
| 'oisy_ic_hide_ethereum_info'
| 'oisy_ic_hide_erc20_info'
- | 'oisy_ic_hide_bitcoin_activity';
+ | 'oisy_ic_hide_bitcoin_activity'
+ | 'oisy_ic_hide_transaction_no_canister';
export const saveHideInfo = (key: HideInfoKey) => {
try {
diff --git a/src/frontend/src/tests/btc/components/convert/BtcConvertFeeTotal.spec.ts b/src/frontend/src/tests/btc/components/convert/BtcConvertFeeTotal.spec.ts
index f74cb27dab..e00a0f9c5c 100644
--- a/src/frontend/src/tests/btc/components/convert/BtcConvertFeeTotal.spec.ts
+++ b/src/frontend/src/tests/btc/components/convert/BtcConvertFeeTotal.spec.ts
@@ -44,31 +44,27 @@ describe('BtcConvertFeeTotal', () => {
store.reset();
});
- it('should calculate totalFee correctly if only default fee is available', () => {
+ it('should not update totalFee if only default fee is available', () => {
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store })
});
- expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(BTC_CONVERT_FEE);
+ expect(component.$$.ctx[component.$$.props['totalFee']]).toBeUndefined();
});
- it('should calculate totalFee correctly if default and utxos fees are available', () => {
+ it('should not update totalFee if only default and utxos fees are available', () => {
store.setUtxosFee({ utxosFee: mockUtxosFee });
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store })
});
- expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(
- BTC_CONVERT_FEE + mockUtxosFee.feeSatoshis
- );
+ expect(component.$$.ctx[component.$$.props['totalFee']]).toBeUndefined();
});
- it('should calculate totalFee correctly if default and ckBTC minter fees are available', () => {
+ it('should not update totalFee if only default and ckBTC minter fees are available', () => {
const tokenId = setupCkBTCStores();
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store, destinationTokenId: tokenId })
});
- expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(
- BTC_CONVERT_FEE + mockCkBtcMinterInfo.kyt_fee
- );
+ expect(component.$$.ctx[component.$$.props['totalFee']]).toBeUndefined();
});
it('should calculate totalFee correctly if all fees are available', () => {
diff --git a/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts b/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts
index 1225849405..c3246330cc 100644
--- a/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts
+++ b/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts
@@ -1,6 +1,8 @@
import { IC_CKBTC_INDEX_CANISTER_ID } from '$env/networks.icrc.env';
import type { IcToken } from '$icp/types/ic-token';
import {
+ hasIndexCanister,
+ hasNoIndexCanister,
isIcCkToken,
isIcToken,
isIcTokenCanistersStrict,
@@ -92,4 +94,32 @@ describe('ic-token.validation', () => {
expect(isNotIcCkToken(mockValidToken)).toBe(true);
});
});
+
+ describe('hasIndexCanister', () => {
+ it('should return false for an IcToken without Index canister', () => {
+ expect(hasIndexCanister(mockValidIcToken)).toBe(false);
+ });
+
+ it('should return true for an IcToken with Index canister', () => {
+ expect(hasIndexCanister(mockValidIcTokenWithIndex)).toBe(true);
+ });
+
+ it('should return false for a token type casted to IcToken', () => {
+ expect(hasIndexCanister(mockValidToken as IcToken)).toBe(false);
+ });
+ });
+
+ describe('hasNoIndexCanister', () => {
+ it('should return true for an IcToken without Index canister', () => {
+ expect(hasNoIndexCanister(mockValidIcToken)).toBe(true);
+ });
+
+ it('should return false for an IcToken with Index canister', () => {
+ expect(hasNoIndexCanister(mockValidIcTokenWithIndex)).toBe(false);
+ });
+
+ it('should return true for a token type casted to IcToken', () => {
+ expect(hasNoIndexCanister(mockValidToken as IcToken)).toBe(true);
+ });
+ });
});
diff --git a/src/frontend/src/tests/lib/components/transactions/AllTransactions.spec.ts b/src/frontend/src/tests/lib/components/transactions/AllTransactions.spec.ts
index ce7b8f7405..e18d3b9222 100644
--- a/src/frontend/src/tests/lib/components/transactions/AllTransactions.spec.ts
+++ b/src/frontend/src/tests/lib/components/transactions/AllTransactions.spec.ts
@@ -1,9 +1,21 @@
+import { icTransactionsStore } from '$icp/stores/ic-transactions.store';
+import { icrcCustomTokensStore } from '$icp/stores/icrc-custom-tokens.store';
+import type { IcrcCustomToken } from '$icp/types/icrc-custom-token';
import AllTransactions from '$lib/components/transactions/AllTransactions.svelte';
+import { replacePlaceholders } from '$lib/utils/i18n.utils';
import en from '$tests/mocks/i18n.mock';
+import { mockValidIcToken } from '$tests/mocks/ic-tokens.mock';
import { assertNonNullish } from '@dfinity/utils';
import { render } from '@testing-library/svelte';
+import { get } from 'svelte/store';
describe('Activity', () => {
+ const customIcrcToken: IcrcCustomToken = {
+ ...mockValidIcToken,
+ version: 1n,
+ enabled: true
+ };
+
it('renders the title', () => {
const { container } = render(AllTransactions);
@@ -15,6 +27,27 @@ describe('Activity', () => {
expect(title.textContent).toBe(en.activity.text.title);
});
+ it('renders the no Index canister warning box', () => {
+ const tokenWithoutIndexCanister: IcrcCustomToken = {
+ ...customIcrcToken,
+ symbol: 'UWT'
+ };
+
+ icrcCustomTokensStore.set({ data: tokenWithoutIndexCanister, certified: true });
+
+ const store = get(icrcCustomTokensStore);
+ const tokenId = store!.at(0)!.data.id;
+ icTransactionsStore.nullify(tokenId);
+
+ const { getByText } = render(AllTransactions);
+
+ const exceptedText = replacePlaceholders(en.activity.warning.no_index_canister, {
+ $token_list: '$UWT'
+ });
+
+ expect(getByText(exceptedText)).toBeInTheDocument();
+ });
+
it('renders the info box list', () => {
const { getByText } = render(AllTransactions);
diff --git a/src/frontend/src/tests/lib/services/auth.services.spec.ts b/src/frontend/src/tests/lib/services/auth.services.spec.ts
new file mode 100644
index 0000000000..70f6385d93
--- /dev/null
+++ b/src/frontend/src/tests/lib/services/auth.services.spec.ts
@@ -0,0 +1,47 @@
+import { signOut } from '$lib/services/auth.services';
+import { authStore } from '$lib/stores/auth.store';
+import { vi } from 'vitest';
+
+const rootLocation = 'https://oisy.com/';
+const activityLocation = 'https://oisy.com/activity';
+
+const mockLocation = (url: string) => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: {
+ href: url,
+ reload: vi.fn()
+ }
+ });
+};
+
+describe('auth.services', () => {
+ describe('signOut', () => {
+ it('should call the signOut function of the authStore without resetting the url', async () => {
+ const signOutSpy = vi.spyOn(authStore, 'signOut');
+
+ mockLocation(activityLocation);
+
+ await signOut({});
+
+ expect(signOutSpy).toHaveBeenCalled();
+ expect(window.location.href).toEqual(activityLocation);
+ });
+
+ it('should call the signOut function of the authStore and resetting the url', async () => {
+ const signOutSpy = vi.spyOn(authStore, 'signOut');
+
+ vi.mock('$lib/utils/nav.utils', () => ({
+ gotoReplaceRoot: () => mockLocation(rootLocation)
+ }));
+
+ mockLocation(activityLocation);
+
+ expect(window.location.href).toEqual(activityLocation);
+ await signOut({ resetUrl: true });
+
+ expect(signOutSpy).toHaveBeenCalled();
+ expect(window.location.href).toEqual(rootLocation);
+ });
+ });
+});