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): add IC transactions to unified list util #3568

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
import { mapAllTransactionsUi, sortTransactions } from '$lib/utils/transactions.utils';

let transactions: AllTransactionsUi;
// TODO: add icTransactions and btcStatuses
$: transactions = mapAllTransactionsUi({
tokens: $enabledTokens,
$btcTransactions: $btcTransactionsStore,
$ethTransactions: $ethTransactionsStore,
$ckEthMinterInfo: $ckEthMinterInfoStore,
$ethAddress: $ethAddress
$ethAddress: $ethAddress,
$icTransactions: {},
$btcStatuses: {}
});

let sortedTransactions: AllTransactionsUi;
Expand Down
35 changes: 31 additions & 4 deletions src/frontend/src/lib/utils/transactions.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import type { EthTransactionsData } from '$eth/stores/eth-transactions.store';
import { mapEthTransactionUi } from '$eth/utils/transactions.utils';
import type { CkEthMinterInfoData } from '$icp-eth/stores/cketh.store';
import { toCkMinterInfoAddresses } from '$icp-eth/utils/cketh.utils';
import IcTransaction from '$icp/components/transactions/IcTransaction.svelte';
import type { BtcStatusesData } from '$icp/stores/btc.store';
import type { IcTransactionUi } from '$icp/types/ic-transaction';
import { normalizeTimestampToSeconds } from '$icp/utils/date.utils';
import { extendIcTransaction } from '$icp/utils/ic-transactions.utils';
import type { CertifiedStoreData } from '$lib/stores/certified.store';
import type { TransactionsData } from '$lib/stores/transactions.store';
import type { OptionEthAddress } from '$lib/types/address';
Expand All @@ -22,26 +26,33 @@ import {
import { isNullish, nonNullish } from '@dfinity/utils';

/**
* Maps the transactions stores to a unified list of transactions with their respective components.
* Maps the transactions stores to a unified list of transactions with their respective token and components.
*
* @param tokens - The tokens to map the transactions for.
* @param $btcTransactions - The BTC transactions store data.
* @param $ethTransactions - The ETH transactions store data.
* @param $ckEthMinterInfo - The CK Ethereum minter info store data.
* @param $ethAddress - The ETH address of the user.
* @param $icTransactions - The ICP transactions store data.
* @param $btcStatuses - The BTC statuses store data.
* @returns The unified list of transactions with their respective token and components.
*/
export const mapAllTransactionsUi = ({
tokens,
$btcTransactions,
$ethTransactions,
$ckEthMinterInfo,
$ethAddress
$ethAddress,
$icTransactions,
$btcStatuses
}: {
tokens: Token[];
$btcTransactions: CertifiedStoreData<TransactionsData<BtcTransactionUi>>;
$ethTransactions: EthTransactionsData;
$ckEthMinterInfo: CertifiedStoreData<CkEthMinterInfoData>;
$ethAddress: OptionEthAddress;
$icTransactions: CertifiedStoreData<TransactionsData<IcTransactionUi>>;
$btcStatuses: CertifiedStoreData<BtcStatusesData>;
}): AllTransactionsUi => {
const ckEthMinterInfoAddressesMainnet = toCkMinterInfoAddresses({
minterInfo: $ckEthMinterInfo?.[ETHEREUM_TOKEN_ID],
Expand Down Expand Up @@ -95,8 +106,24 @@ export const mapAllTransactionsUi = ({
}

if (isNetworkIdICP(networkId)) {
// TODO: Implement ICP transactions
return acc;
// TODO: Implement ckBTC and ckETH pending transactions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsolicited feedback: the more you provide PR for this feature, the more I think it's unrealisitc in terms of performance but, let's see.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes... the final version is not exactly good; we should really start thinking about caching


if (isNullish($icTransactions)) {
return acc;
}

return [
...acc,
...($icTransactions[tokenId] ?? []).map((transaction) => ({
...extendIcTransaction({
transaction,
token,
btcStatuses: $btcStatuses?.[tokenId] ?? undefined
}).data,
token,
component: IcTransaction
}))
];
}

return acc;
Expand Down
81 changes: 75 additions & 6 deletions src/frontend/src/tests/lib/utils/transactions.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,30 @@ import {
import {
ETHEREUM_TOKEN,
ETHEREUM_TOKEN_ID,
ICP_TOKEN,
ICP_TOKEN_ID,
SEPOLIA_TOKEN,
SEPOLIA_TOKEN_ID
} from '$env/tokens.env';
import EthTransaction from '$eth/components/transactions/EthTransaction.svelte';
import type { EthTransactionsData } from '$eth/stores/eth-transactions.store';
import type { EthTransactionType } from '$eth/types/eth-transaction';
import IcTransaction from '$icp/components/transactions/IcTransaction.svelte';
import type { IcTransactionUi } from '$icp/types/ic-transaction';
import type { CertifiedStoreData } from '$lib/stores/certified.store';
import type { TransactionsData } from '$lib/stores/transactions.store';
import type { AllTransactionsUi, AnyTransactionUi, Transaction } from '$lib/types/transaction';
import { mapAllTransactionsUi, sortTransactions } from '$lib/utils/transactions.utils';
import { createMockBtcTransactionsUi } from '$tests/mocks/btc.mock';
import { createMockEthTransactions } from '$tests/mocks/eth-transactions.mock';
import { createMockIcTransactionsUi } from '$tests/mocks/ic-transactions.mock';

describe('transactions.utils', () => {
describe('mapAllTransactionsUi', () => {
const btcTokens = [BTC_MAINNET_TOKEN, BTC_TESTNET_TOKEN];
const ethTokens = [ETHEREUM_TOKEN, SEPOLIA_TOKEN, PEPE_TOKEN];
const tokens = [...btcTokens, ...ethTokens];
const icTokens = [ICP_TOKEN];
const tokens = [...btcTokens, ...ethTokens, ...icTokens];

const certified = false;

Expand All @@ -54,6 +60,12 @@ describe('transactions.utils', () => {
[PEPE_TOKEN_ID]: mockErc20Transactions
};

const mockIcTransactionsUi: IcTransactionUi[] = createMockIcTransactionsUi(7);

const mockIcTransactions: CertifiedStoreData<TransactionsData<IcTransactionUi>> = {
[ICP_TOKEN_ID]: mockIcTransactionsUi.map((data) => ({ data, certified }))
};

const expectedBtcMainnetTransactions: AllTransactionsUi = [
...mockBtcMainnetTransactions.map((transaction) => ({
...transaction,
Expand Down Expand Up @@ -100,9 +112,18 @@ describe('transactions.utils', () => {
...expectedErc20Transactions
];

const expectedIcTransactions: AllTransactionsUi = [
...mockIcTransactionsUi.map((transaction) => ({
...transaction,
token: ICP_TOKEN,
component: IcTransaction
}))
];

const expectedTransactions: AllTransactionsUi = [
...expectedBtcMainnetTransactions,
...expectedEthTransactions
...expectedEthTransactions,
...expectedIcTransactions
];

beforeEach(() => {
Expand All @@ -117,7 +138,13 @@ describe('transactions.utils', () => {
describe('BTC transactions', () => {
const tokens = [...btcTokens];

const rest = { $ethTransactions: {}, $ckEthMinterInfo: {}, $ethAddress: undefined };
const rest = {
$ethTransactions: {},
$ckEthMinterInfo: {},
$ethAddress: undefined,
$icTransactions: {},
$btcStatuses: undefined
};

it('should map BTC mainnet transactions correctly', () => {
const result = mapAllTransactionsUi({
Expand Down Expand Up @@ -154,7 +181,13 @@ describe('transactions.utils', () => {
describe('ETH transactions', () => {
const tokens = [...ethTokens];

const rest = { $btcTransactions: undefined, $ckEthMinterInfo: {}, $ethAddress: undefined };
const rest = {
$btcTransactions: undefined,
$ckEthMinterInfo: {},
$ethAddress: undefined,
$icTransactions: {},
$btcStatuses: undefined
};

it('should map ETH transactions correctly', () => {
const result = mapAllTransactionsUi({
Expand Down Expand Up @@ -212,21 +245,57 @@ describe('transactions.utils', () => {
});
});

describe('IC transactions', () => {
const tokens = [...icTokens];

const rest = {
$btcTransactions: undefined,
$ckEthMinterInfo: {},
$ethTransactions: {},
$ethAddress: undefined,
$btcStatuses: undefined
};

it('should map IC transactions correctly', () => {
const result = mapAllTransactionsUi({
tokens,
$icTransactions: mockIcTransactions,
...rest
});

expect(result).toHaveLength(mockIcTransactionsUi.length);
expect(result).toEqual(expectedIcTransactions);
});

it('should return an empty array if the IC transactions store is not initialized', () => {
const result = mapAllTransactionsUi({
tokens,
$icTransactions: {},
...rest
});

expect(result).toEqual([]);
});
});

describe('mixed transactions', () => {
it('should map all transactions correctly', () => {
const result = mapAllTransactionsUi({
tokens,
$btcTransactions: mockBtcTransactions,
$ethTransactions: mockEthTransactions,
$ckEthMinterInfo: {},
$ethAddress: undefined
$ethAddress: undefined,
$icTransactions: mockIcTransactions,
$btcStatuses: undefined
});

expect(result).toHaveLength(
mockBtcMainnetTransactions.length +
mockEthMainnetTransactions.length +
mockSepoliaTransactions.length +
mockErc20Transactions.length
mockErc20Transactions.length +
mockIcTransactionsUi.length
);

expect(result).toEqual(expectedTransactions);
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/src/tests/mocks/ic-transactions.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { IcTransactionUi } from '$icp/types/ic-transaction';

export const createMockIcTransactionsUi = (n: number): IcTransactionUi[] =>
Array.from({ length: n }, () => ({
id: crypto.randomUUID(),
type: 'send',
status: 'executed'
}));