Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions icons/RPC.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions lib/web3/rpc/formatBlockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { Chain, GetBlockReturnType } from 'viem';

import type { Block } from 'types/api/block';

import dayjs from 'lib/date/dayjs';
import { unknownAddress } from 'ui/shared/address/utils';

export default function formatBlockData(block: GetBlockReturnType<Chain, false, 'latest'> | null): Block | null {
if (!block) {
return null;
}

return {
height: Number(block.number),
timestamp: dayjs.unix(Number(block.timestamp)).format(),
transactions_count: block.transactions.length,
internal_transactions_count: 0,
miner: { ...unknownAddress, hash: block.miner },
size: Number(block.size),
hash: block.hash,
parent_hash: block.parentHash,
difficulty: block.difficulty?.toString() ?? null,
total_difficulty: block.totalDifficulty?.toString() ?? null,
gas_used: block.gasUsed.toString(),
gas_limit: block.gasLimit.toString(),
nonce: block.nonce,
base_fee_per_gas: block.baseFeePerGas?.toString() ?? null,
burnt_fees: null,
priority_fee: null,
extra_data: block.extraData,
state_root: block.stateRoot,
gas_target_percentage: null,
gas_used_percentage: null,
burnt_fees_percentage: null,
type: 'block', // we can't get this type from RPC, so it will always be a regular block
transaction_fees: null,
uncles_hashes: block.uncles,
withdrawals_count: block.withdrawals?.length,
};
}
66 changes: 66 additions & 0 deletions lib/web3/rpc/formatTxData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Chain, GetTransactionReturnType, TransactionReceipt } from 'viem';

import type { Transaction } from 'types/api/transaction';

import dayjs from 'lib/date/dayjs';
import hexToDecimal from 'lib/hexToDecimal';
import { unknownAddress } from 'ui/shared/address/utils';

export default function formatTxData(
tx: GetTransactionReturnType<Chain, 'latest'>,
receipt: TransactionReceipt | null,
confirmations: bigint | null,
block: { timestamp: bigint | null; baseFeePerGas: bigint | null | undefined } | null,
): Transaction | null {
const status = (() => {
if (!receipt) {
return null;
}

return receipt.status === 'success' ? 'ok' : 'error';
})();

const gasPrice = receipt?.effectiveGasPrice ?? tx.gasPrice;

return {
from: { ...unknownAddress, hash: tx.from as string },
to: tx.to ? { ...unknownAddress, hash: tx.to as string } : null,
hash: tx.hash as string,
timestamp: block?.timestamp ? dayjs.unix(Number(block.timestamp)).format() : null,
confirmation_duration: null,
status,
block_number: tx.blockNumber ? Number(tx.blockNumber) : null,
value: tx.value.toString(),
gas_price: gasPrice?.toString() ?? null,
base_fee_per_gas: block?.baseFeePerGas?.toString() ?? null,
max_fee_per_gas: tx.maxFeePerGas?.toString() ?? null,
max_priority_fee_per_gas: tx.maxPriorityFeePerGas?.toString() ?? null,
nonce: tx.nonce,
position: tx.transactionIndex,
type: tx.typeHex ? hexToDecimal(tx.typeHex) : null,
raw_input: tx.input,
gas_used: receipt?.gasUsed?.toString() ?? null,
gas_limit: tx.gas.toString(),
confirmations: confirmations && confirmations > 0 ? Number(confirmations) : 0,
fee: {
value: receipt && gasPrice ? (receipt.gasUsed * gasPrice).toString() : null,
type: 'actual',
},
created_contract: receipt?.contractAddress ?
{ ...unknownAddress, hash: receipt.contractAddress, is_contract: true } :
null,
result: '',
priority_fee: null,
transaction_burnt_fee: null,
revert_reason: null,
decoded_input: null,
has_error_in_internal_transactions: null,
token_transfers: null,
token_transfers_overflow: false,
exchange_rate: null,
method: null,
transaction_types: [],
transaction_tag: null,
actions: [],
};
}
19 changes: 19 additions & 0 deletions mocks/txs/tx.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* eslint-disable max-len */
import type { RpcTransactionReceipt } from 'viem';

import type { AddressParam } from 'types/api/addressParams';
import type { Transaction } from 'types/api/transaction';

Expand Down Expand Up @@ -476,3 +478,20 @@ export const withInteropOutMessage: Transaction = {
target_address_hash: addressMock.hash,
} ],
};

export const rpcTxReceipt: RpcTransactionReceipt = {
blockHash: '0xa737203aac9f38b5355c716f46b84ff1031335d1a99b2366900378c9e4c837a5',
blockNumber: '0x171f82b',
contractAddress: null,
cumulativeGasUsed: '0xe235b',
effectiveGasPrice: '0x793b22f4',
from: '0x21dc71ddd3558cd7536bb5fa422303fb5559ea63',
gasUsed: '0x215d2',
logs: [],
logsBloom: '0x00400040000000000000000000000000000000000000000010000000000000000000000020000000001000000000010400000000000000000000000000000000000400000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100000000000002801000000000000000000000000000000000000000000000000100000000004000000000000000080100000000000000000000000000000000000000000000802000000000000000000000000000000400000000000000000000000000000000000000000000000000000000004000000800000000000004000000000',
status: 'success' as RpcTransactionReceipt['status'],
to: '0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e',
transactionHash: '0xc39cf2777f03346deba8659b3ff652bbcf3dcbd4fcf846a248a171e45cac94b2',
transactionIndex: '0x2',
type: '0x2',
};
16 changes: 15 additions & 1 deletion playwright/fixtures/mockRpcResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PublicRpcSchema } from 'viem';

import { getEnvValue } from 'configs/app/utils';

type Params = Array<PublicRpcSchema[number]>;
type Params = Array<PublicRpcSchema[number] & { status?: number }>;

export type MockRpcResponseFixture = (params: Params) => Promise<void>;

Expand Down Expand Up @@ -44,6 +44,20 @@ const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ pag
});

if (rpcMock) {
if (rpcMock.status && rpcMock.status !== 200) {
return route.fulfill({
status: rpcMock.status,
json: {
id,
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Internal error',
},
},
});
}

return route.fulfill({
status: 200,
json: {
Expand Down
4 changes: 2 additions & 2 deletions stubs/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ export const HOMEPAGE_STATS_MICROSERVICE: stats.MainPageStats = {
yesterday_operational_transactions: STATS_COUNTER,
daily_new_transactions: {
chart: [],
info: STATS_CHART_INFO,
info: { ...STATS_CHART_INFO, title: 'Daily transactions' },
},
daily_new_operational_transactions: {
chart: [],
info: STATS_CHART_INFO,
info: { ...STATS_CHART_INFO, title: 'Daily op txns' },
},
};
35 changes: 2 additions & 33 deletions ui/block/useBlockQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import type { Block } from 'types/api/block';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import { retry } from 'lib/api/useQueryClientConfig';
import dayjs from 'lib/date/dayjs';
import { publicClient } from 'lib/web3/client';
import formatBlockData from 'lib/web3/rpc/formatBlockData';
import { BLOCK } from 'stubs/block';
import { GET_BLOCK } from 'stubs/RPC';
import { SECOND } from 'toolkit/utils/consts';
import { unknownAddress } from 'ui/shared/address/utils';

type RpcResponseType = GetBlockReturnType<Chain, false, 'latest'> | null;

Expand Down Expand Up @@ -70,37 +69,7 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery {
return publicClient.getBlock(blockParams).catch(() => null);
},
select: (block) => {
if (!block) {
return null;
}

return {
height: Number(block.number),
timestamp: dayjs.unix(Number(block.timestamp)).format(),
transactions_count: block.transactions.length,
internal_transactions_count: 0,
miner: { ...unknownAddress, hash: block.miner },
size: Number(block.size),
hash: block.hash,
parent_hash: block.parentHash,
difficulty: block.difficulty?.toString() ?? null,
total_difficulty: block.totalDifficulty?.toString() ?? null,
gas_used: block.gasUsed.toString(),
gas_limit: block.gasLimit.toString(),
nonce: block.nonce,
base_fee_per_gas: block.baseFeePerGas?.toString() ?? null,
burnt_fees: null,
priority_fee: null,
extra_data: block.extraData,
state_root: block.stateRoot,
gas_target_percentage: null,
gas_used_percentage: null,
burnt_fees_percentage: null,
type: 'block', // we can't get this type from RPC, so it will always be a regular block
transaction_fees: null,
uncles_hashes: block.uncles,
withdrawals_count: block.withdrawals?.length,
};
return formatBlockData(block);
},
placeholderData: GET_BLOCK,
enabled: !latestBlockQuery.isPending,
Expand Down
33 changes: 28 additions & 5 deletions ui/home/LatestBlocks.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,52 @@ import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import * as socketServer from 'playwright/fixtures/socketServer';
import { test, expect } from 'playwright/lib';

import { HomeRpcDataContextProvider } from './fallbacks/rpcDataContext';
import LatestBlocks from './LatestBlocks';

test('default view +@mobile +@dark-mode', async({ render, mockApiResponse }) => {
await mockApiResponse('general:stats', statsMock.base);
await mockApiResponse('general:homepage_blocks', [ blockMock.base, blockMock.base2 ]);
const component = await render(<LatestBlocks/>);
const component = await render(
<HomeRpcDataContextProvider>
<LatestBlocks/>
</HomeRpcDataContextProvider>,
);
await expect(component).toHaveScreenshot();
});

test('L2 view', async({ render, mockEnvs, mockApiResponse }) => {
await mockEnvs(ENVS_MAP.optimisticRollup);
await mockApiResponse('general:stats', statsMock.base);
await mockApiResponse('general:homepage_blocks', [ blockMock.base, blockMock.base2 ]);
const component = await render(<LatestBlocks/>);
const component = await render(
<HomeRpcDataContextProvider>
<LatestBlocks/>
</HomeRpcDataContextProvider>,
);
await expect(component).toHaveScreenshot();
});

test('no reward view', async({ render, mockEnvs, mockApiResponse }) => {
await mockEnvs(ENVS_MAP.blockHiddenFields);
await mockApiResponse('general:stats', statsMock.base);
await mockApiResponse('general:homepage_blocks', [ blockMock.base, blockMock.base2 ]);
const component = await render(<LatestBlocks/>);
const component = await render(
<HomeRpcDataContextProvider>
<LatestBlocks/>
</HomeRpcDataContextProvider>,
);
await expect(component).toHaveScreenshot();
});

test('with long block height', async({ render, mockApiResponse }) => {
await mockApiResponse('general:stats', statsMock.base);
await mockApiResponse('general:homepage_blocks', [ { ...blockMock.base, height: 123456789012345 } ]);
const component = await render(<LatestBlocks/>);
const component = await render(
<HomeRpcDataContextProvider>
<LatestBlocks/>
</HomeRpcDataContextProvider>,
);
await expect(component).toHaveScreenshot();
});

Expand All @@ -43,7 +60,13 @@ test.describe('socket', () => {
test('new item', async({ render, mockApiResponse, createSocket }) => {
await mockApiResponse('general:stats', statsMock.base);
await mockApiResponse('general:homepage_blocks', [ blockMock.base, blockMock.base2 ]);
const component = await render(<LatestBlocks/>, undefined, { withSocket: true });
const component = await render(
<HomeRpcDataContextProvider>
<LatestBlocks/>
</HomeRpcDataContextProvider>,
undefined,
{ withSocket: true },
);
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'blocks:new_block');
socketServer.sendMessage(socket, channel, 'new_block', {
Expand Down
Loading
Loading