@@ -10,6 +10,7 @@ on:
|
||||
type: choice
|
||||
options:
|
||||
- none
|
||||
- arbitrum
|
||||
- base
|
||||
- gnosis
|
||||
- eth
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Set of ENVs for Optimism (dev only)
|
||||
# https://optimism.blockscout.com/
|
||||
|
||||
# app configuration
|
||||
NEXT_PUBLIC_APP_PROTOCOL=http
|
||||
NEXT_PUBLIC_APP_HOST=localhost
|
||||
NEXT_PUBLIC_APP_PORT=3000
|
||||
|
||||
# blockchain parameters
|
||||
NEXT_PUBLIC_NETWORK_NAME=Arbitrum One Nitro
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum One Nitro
|
||||
NEXT_PUBLIC_NETWORK_ID=42161
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
|
||||
# api configuration
|
||||
NEXT_PUBLIC_API_HOST=arbitrum.blockscout.com
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
|
||||
# ui config
|
||||
## homepage
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
## views
|
||||
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
|
||||
# app features
|
||||
NEXT_PUBLIC_APP_INSTANCE=local
|
||||
NEXT_PUBLIC_APP_ENV=development
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
|
||||
NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
|
||||
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
|
||||
# rollup
|
||||
NEXT_PUBLIC_ROLLUP_TYPE=arbitrum
|
||||
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER=hype
|
||||
@@ -400,7 +400,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | Required only for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (OP stack only) | - | - | `true` | v1.31.0+ |
|
||||
|
||||
@@ -33,6 +33,13 @@ import type {
|
||||
} from 'types/api/address';
|
||||
import type { AddressesResponse } from 'types/api/addresses';
|
||||
import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata';
|
||||
import type {
|
||||
ArbitrumL2MessagesResponse,
|
||||
ArbitrumL2TxnBatch,
|
||||
ArbitrumL2TxnBatchesResponse,
|
||||
ArbitrumL2BatchTxs,
|
||||
ArbitrumL2BatchBlocks,
|
||||
} from 'types/api/arbitrumL2';
|
||||
import type { TxBlobs, Blob } from 'types/api/blobs';
|
||||
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block';
|
||||
import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts';
|
||||
@@ -663,6 +670,44 @@ export const RESOURCES = {
|
||||
path: '/api/v2/optimism/games/count',
|
||||
},
|
||||
|
||||
// arbitrum L2
|
||||
arbitrum_l2_messages: {
|
||||
path: '/api/v2/arbitrum/messages/:direction',
|
||||
pathParams: [ 'direction' as const ],
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
arbitrum_l2_messages_count: {
|
||||
path: '/api/v2/arbitrum/messages/:direction/count',
|
||||
pathParams: [ 'direction' as const ],
|
||||
},
|
||||
|
||||
arbitrum_l2_txn_batches: {
|
||||
path: '/api/v2/arbitrum/batches',
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
arbitrum_l2_txn_batches_count: {
|
||||
path: '/api/v2/arbitrum/batches/count',
|
||||
},
|
||||
|
||||
arbitrum_l2_txn_batch: {
|
||||
path: '/api/v2/arbitrum/batches/:number',
|
||||
pathParams: [ 'number' as const ],
|
||||
},
|
||||
|
||||
arbitrum_l2_txn_batch_txs: {
|
||||
path: '/api/v2/transactions/arbitrum-batch/:number',
|
||||
pathParams: [ 'number' as const ],
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
arbitrum_l2_txn_batch_blocks: {
|
||||
path: '/api/v2/blocks/arbitrum-batch/:number',
|
||||
pathParams: [ 'number' as const ],
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
// zkEvm L2
|
||||
zkevm_l2_deposits: {
|
||||
path: '/api/v2/zkevm/deposits',
|
||||
@@ -868,6 +913,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
|
||||
'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' |
|
||||
'optimistic_l2_dispute_games' |
|
||||
'shibarium_deposits' | 'shibarium_withdrawals' |
|
||||
'arbitrum_l2_messages' | 'arbitrum_l2_txn_batches' | 'arbitrum_l2_txn_batch_txs' | 'arbitrum_l2_txn_batch_blocks' |
|
||||
'zkevm_l2_deposits' | 'zkevm_l2_withdrawals' | 'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' |
|
||||
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
|
||||
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
|
||||
@@ -994,6 +1040,13 @@ Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse :
|
||||
Q extends 'shibarium_deposits' ? ShibariumDepositsResponse :
|
||||
Q extends 'shibarium_withdrawals_count' ? number :
|
||||
Q extends 'shibarium_deposits_count' ? number :
|
||||
Q extends 'arbitrum_l2_messages' ? ArbitrumL2MessagesResponse :
|
||||
Q extends 'arbitrum_l2_messages_count' ? number :
|
||||
Q extends 'arbitrum_l2_txn_batches' ? ArbitrumL2TxnBatchesResponse :
|
||||
Q extends 'arbitrum_l2_txn_batches_count' ? number :
|
||||
Q extends 'arbitrum_l2_txn_batch' ? ArbitrumL2TxnBatch :
|
||||
Q extends 'arbitrum_l2_txn_batch_txs' ? ArbitrumL2BatchTxs :
|
||||
Q extends 'arbitrum_l2_txn_batch_blocks' ? ArbitrumL2BatchBlocks :
|
||||
Q extends 'zkevm_l2_deposits' ? ZkEvmL2DepositsResponse :
|
||||
Q extends 'zkevm_l2_deposits_count' ? number :
|
||||
Q extends 'zkevm_l2_withdrawals' ? ZkEvmL2WithdrawalsResponse :
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ArbitrumBatchStatus, ArbitrumL2TxData } from 'types/api/arbitrumL2';
|
||||
|
||||
type Args = {
|
||||
status: ArbitrumBatchStatus;
|
||||
commitment_transaction: ArbitrumL2TxData;
|
||||
confirmation_transaction: ArbitrumL2TxData;
|
||||
}
|
||||
|
||||
export default function getArbitrumVerificationStepStatus({
|
||||
status,
|
||||
commitment_transaction: commitTx,
|
||||
confirmation_transaction: confirmTx,
|
||||
}: Args) {
|
||||
if (status === 'Sent to base') {
|
||||
if (commitTx.status === 'unfinalized') {
|
||||
return 'pending';
|
||||
}
|
||||
}
|
||||
if (status === 'Confirmed on base') {
|
||||
if (confirmTx.status === 'unfinalized') {
|
||||
return 'pending';
|
||||
}
|
||||
}
|
||||
return 'finalized';
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export default function useNavItems(): ReturnType {
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
if (rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'zkEvm')) {
|
||||
if (rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum' || rollupFeature.type === 'zkEvm')) {
|
||||
blockchainNavItems = [
|
||||
[
|
||||
txs,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ArbitrumL2MessagesResponse } from 'types/api/arbitrumL2';
|
||||
|
||||
export const baseResponse: ArbitrumL2MessagesResponse = {
|
||||
items: [
|
||||
{
|
||||
completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e',
|
||||
id: 181920,
|
||||
origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
|
||||
origination_transaction_block_number: 123456,
|
||||
origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436',
|
||||
origination_timestamp: '2023-06-01T14:46:48.000000Z',
|
||||
status: 'initiated',
|
||||
},
|
||||
{
|
||||
completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e',
|
||||
id: 181921,
|
||||
origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
|
||||
origination_transaction_block_number: 123400,
|
||||
origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436',
|
||||
origination_timestamp: '2023-06-01T14:46:48.000000Z',
|
||||
status: 'relayed',
|
||||
},
|
||||
],
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
id: 123,
|
||||
direction: 'to-rollup',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2';
|
||||
|
||||
import { finalized } from './txnBatches';
|
||||
|
||||
export const batchData: ArbitrumL2TxnBatch = {
|
||||
...finalized,
|
||||
after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb',
|
||||
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
|
||||
start_block: 1245209,
|
||||
end_block: 1245490,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { ArbitrumL2TxnBatchesItem, ArbitrumL2TxnBatchesResponse } from 'types/api/arbitrumL2';
|
||||
|
||||
export const finalized: ArbitrumL2TxnBatchesItem = {
|
||||
number: 12345,
|
||||
block_count: 12345,
|
||||
transactions_count: 10000,
|
||||
commitment_transaction: {
|
||||
block_number: 12345,
|
||||
timestamp: '2022-04-17T08:51:58.000000Z',
|
||||
hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661',
|
||||
status: 'finalized',
|
||||
},
|
||||
};
|
||||
|
||||
export const unfinalized: ArbitrumL2TxnBatchesItem = {
|
||||
number: 12344,
|
||||
block_count: 10000,
|
||||
transactions_count: 103020,
|
||||
commitment_transaction: {
|
||||
block_number: 12340,
|
||||
timestamp: '2022-04-17T08:51:58.000000Z',
|
||||
hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661',
|
||||
status: 'unfinalized',
|
||||
},
|
||||
};
|
||||
|
||||
export const baseResponse: ArbitrumL2TxnBatchesResponse = {
|
||||
items: [
|
||||
finalized,
|
||||
unfinalized,
|
||||
],
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
number: 123,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ArbitrumL2MessagesResponse } from 'types/api/arbitrumL2';
|
||||
|
||||
export const baseResponse: ArbitrumL2MessagesResponse = {
|
||||
items: [
|
||||
{
|
||||
completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e',
|
||||
id: 181920,
|
||||
origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
|
||||
origination_transaction_block_number: 123456,
|
||||
origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436',
|
||||
origination_timestamp: '2023-06-01T14:46:48.000000Z',
|
||||
status: 'sent',
|
||||
},
|
||||
{
|
||||
completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e',
|
||||
id: 181921,
|
||||
origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
|
||||
origination_transaction_block_number: 123400,
|
||||
origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436',
|
||||
origination_timestamp: '2023-06-01T14:46:48.000000Z',
|
||||
status: 'confirmed',
|
||||
},
|
||||
],
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
id: 123,
|
||||
direction: 'from-rollup',
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
|
||||
|
||||
import type { AdBannerProviders } from 'types/client/adProviders';
|
||||
import type { RollupType } from 'types/client/rollup';
|
||||
|
||||
import type { Route } from 'nextjs-routes';
|
||||
|
||||
@@ -66,8 +67,9 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
|
||||
return account(context);
|
||||
};
|
||||
|
||||
const DEPOSITS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum' ];
|
||||
export const deposits: GetServerSideProps<Props> = async(context) => {
|
||||
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium' || rollupFeature.type === 'zkEvm'))) {
|
||||
if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
@@ -76,10 +78,11 @@ export const deposits: GetServerSideProps<Props> = async(context) => {
|
||||
return base(context);
|
||||
};
|
||||
|
||||
const WITHDRAWALS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum' ];
|
||||
export const withdrawals: GetServerSideProps<Props> = async(context) => {
|
||||
if (
|
||||
!config.features.beaconChain.isEnabled &&
|
||||
!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium' || rollupFeature.type === 'zkEvm'))
|
||||
!(rollupFeature.isEnabled && WITHDRAWALS_ROLLUP_TYPES.includes(rollupFeature.type))
|
||||
) {
|
||||
return {
|
||||
notFound: true,
|
||||
@@ -109,8 +112,9 @@ export const optimisticRollup: GetServerSideProps<Props> = async(context) => {
|
||||
return base(context);
|
||||
};
|
||||
|
||||
const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum' ];
|
||||
export const batch: GetServerSideProps<Props> = async(context) => {
|
||||
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync'))) {
|
||||
if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@ const Batch = dynamic(() => {
|
||||
}
|
||||
|
||||
switch (rollupFeature.type) {
|
||||
case 'arbitrum':
|
||||
return import('ui/pages/ArbitrumL2TxnBatch');
|
||||
case 'zkEvm':
|
||||
return import('ui/pages/ZkEvmL2TxnBatch');
|
||||
case 'zkSync':
|
||||
|
||||
@@ -19,6 +19,8 @@ const Batches = dynamic(() => {
|
||||
return import('ui/pages/ZkSyncL2TxnBatches');
|
||||
case 'optimistic':
|
||||
return import('ui/pages/OptimisticL2TxnBatches');
|
||||
case 'arbitrum':
|
||||
return import('ui/pages/ArbitrumL2TxnBatches');
|
||||
}
|
||||
throw new Error('Txn batches feature is not enabled.');
|
||||
}, { ssr: false });
|
||||
|
||||
@@ -12,6 +12,10 @@ const Deposits = dynamic(() => {
|
||||
return import('ui/pages/OptimisticL2Deposits');
|
||||
}
|
||||
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'arbitrum') {
|
||||
return import('ui/pages/ArbitrumL2Deposits');
|
||||
}
|
||||
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
|
||||
return import('ui/pages/ShibariumDeposits');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ const Withdrawals = dynamic(() => {
|
||||
return import('ui/pages/OptimisticL2Withdrawals');
|
||||
}
|
||||
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'arbitrum') {
|
||||
return import('ui/pages/ArbitrumL2Withdrawals');
|
||||
}
|
||||
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
|
||||
return import('ui/pages/ShibariumWithdrawals');
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
|
||||
[ 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', 'https://localhost:3102' ],
|
||||
[ 'NEXT_PUBLIC_FAULT_PROOF_ENABLED', 'true' ],
|
||||
],
|
||||
arbitrumRollup: [
|
||||
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'arbitrum' ],
|
||||
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
|
||||
],
|
||||
shibariumRollup: [
|
||||
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ],
|
||||
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { ArbitrumL2TxnBatchesItem, ArbitrumL2TxnBatch, ArbitrumL2MessagesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import { ADDRESS_HASH } from './addressParams';
|
||||
import { TX_HASH } from './tx';
|
||||
|
||||
export const ARBITRUM_MESSAGES_ITEM: ArbitrumL2MessagesItem = {
|
||||
completion_transaction_hash: TX_HASH,
|
||||
id: 181920,
|
||||
origination_address: ADDRESS_HASH,
|
||||
origination_transaction_block_number: 123456,
|
||||
origination_transaction_hash: TX_HASH,
|
||||
origination_timestamp: '2023-06-01T14:46:48.000000Z',
|
||||
status: 'relayed',
|
||||
};
|
||||
|
||||
export const ARBITRUM_L2_TXN_BATCHES_ITEM: ArbitrumL2TxnBatchesItem = {
|
||||
number: 12345,
|
||||
block_count: 12345,
|
||||
transactions_count: 10000,
|
||||
commitment_transaction: {
|
||||
block_number: 12345,
|
||||
timestamp: '2024-04-17T08:51:58.000000Z',
|
||||
hash: TX_HASH,
|
||||
status: 'finalized',
|
||||
},
|
||||
};
|
||||
|
||||
export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = {
|
||||
...ARBITRUM_L2_TXN_BATCHES_ITEM,
|
||||
after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb',
|
||||
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
|
||||
start_block: 1245209,
|
||||
end_block: 1245490,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { Block } from './block';
|
||||
import type { Transaction } from './transaction';
|
||||
|
||||
export type ArbitrumL2MessagesItem = {
|
||||
completion_transaction_hash: string | null;
|
||||
id: number;
|
||||
origination_address: string;
|
||||
origination_timestamp: string | null;
|
||||
origination_transaction_block_number: number;
|
||||
origination_transaction_hash: string;
|
||||
status: 'initiated' | 'sent' | 'confirmed' | 'relayed';
|
||||
}
|
||||
|
||||
export type ArbitrumL2MessagesResponse = {
|
||||
items: Array<ArbitrumL2MessagesItem>;
|
||||
next_page_params: {
|
||||
direction: string;
|
||||
id: number;
|
||||
items_count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type ArbitrumL2TxData = {
|
||||
hash: string | null;
|
||||
status: string | null;
|
||||
timestamp: string | null;
|
||||
}
|
||||
|
||||
type ArbitrumL2BatchCommitmentTx = {
|
||||
block_number: number;
|
||||
hash: string;
|
||||
status: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export type ArbitrumL2TxnBatchesItem = {
|
||||
block_count: number;
|
||||
commitment_transaction: ArbitrumL2BatchCommitmentTx;
|
||||
number: number;
|
||||
transactions_count: number;
|
||||
}
|
||||
|
||||
export type ArbitrumL2TxnBatchesResponse = {
|
||||
items: Array<ArbitrumL2TxnBatchesItem>;
|
||||
next_page_params: {
|
||||
number: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export type ArbitrumL2TxnBatch = {
|
||||
after_acc: string;
|
||||
before_acc: string;
|
||||
commitment_transaction: ArbitrumL2BatchCommitmentTx;
|
||||
end_block: number;
|
||||
start_block: number;
|
||||
number: number;
|
||||
transactions_count: number;
|
||||
}
|
||||
|
||||
export type ArbitrumL2BatchTxs = {
|
||||
items: Array<Transaction>;
|
||||
next_page_params: {
|
||||
batch_number: string;
|
||||
block_number: number;
|
||||
index: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export type ArbitrumL2BatchBlocks = {
|
||||
items: Array<Block>;
|
||||
next_page_params: {
|
||||
batch_number: string;
|
||||
block_number: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const ARBITRUM_L2_TX_BATCH_STATUSES = [
|
||||
'Processed on rollup' as const,
|
||||
'Sent to base' as const,
|
||||
'Confirmed on base' as const,
|
||||
];
|
||||
|
||||
export type ArbitrumBatchStatus = typeof ARBITRUM_L2_TX_BATCH_STATUSES[number];
|
||||
@@ -2,6 +2,7 @@ import type { AddressParam } from 'types/api/addressParams';
|
||||
import type { Reward } from 'types/api/reward';
|
||||
import type { Transaction } from 'types/api/transaction';
|
||||
|
||||
import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2';
|
||||
import type { ZkSyncBatchesItem } from './zkSyncL2';
|
||||
|
||||
export type BlockType = 'block' | 'reorg' | 'uncle';
|
||||
@@ -48,6 +49,18 @@ export interface Block {
|
||||
zksync?: Omit<ZkSyncBatchesItem, 'number' | 'tx_count' | 'timestamp'> & {
|
||||
'batch_number': number | null;
|
||||
};
|
||||
arbitrum?: ArbitrumBlockData;
|
||||
}
|
||||
|
||||
type ArbitrumBlockData = {
|
||||
'batch_number': number;
|
||||
'commitment_transaction': ArbitrumL2TxData;
|
||||
'confirmation_transaction': ArbitrumL2TxData;
|
||||
'delayed_messages': number;
|
||||
'l1_block_height': number;
|
||||
'send_count': number;
|
||||
'send_root': string;
|
||||
'status': ArbitrumBatchStatus;
|
||||
}
|
||||
|
||||
export interface BlocksResponse {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AddressParam } from './addressParams';
|
||||
import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2';
|
||||
import type { BlockTransactionsResponse } from './block';
|
||||
import type { DecodedInput } from './decodedInput';
|
||||
import type { Fee } from './fee';
|
||||
@@ -93,6 +94,19 @@ export type Transaction = {
|
||||
max_fee_per_blob_gas?: string;
|
||||
// Noves-fi
|
||||
translation?: NovesTxTranslation;
|
||||
arbitrum?: ArbitrumTransactionData;
|
||||
}
|
||||
|
||||
type ArbitrumTransactionData = {
|
||||
batch_number: number;
|
||||
commitment_transaction: ArbitrumL2TxData;
|
||||
confirmation_transaction: ArbitrumL2TxData;
|
||||
contains_message: 'incoming' | 'outcoming' | null;
|
||||
gas_used_for_l1: string;
|
||||
gas_used_for_l2: string;
|
||||
network_fee: string;
|
||||
poster_fee: string;
|
||||
status: ArbitrumBatchStatus;
|
||||
}
|
||||
|
||||
export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ];
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils';
|
||||
|
||||
export const ROLLUP_TYPES = [
|
||||
'optimistic',
|
||||
'arbitrum',
|
||||
'shibarium',
|
||||
'zkEvm',
|
||||
'zkSync',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import { scroller, Element } from 'react-scroll';
|
||||
|
||||
import { ARBITRUM_L2_TX_BATCH_STATUSES } from 'types/api/arbitrumL2';
|
||||
import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
@@ -12,6 +13,7 @@ import { route } from 'nextjs-routes';
|
||||
import config from 'configs/app';
|
||||
import getBlockReward from 'lib/block/getBlockReward';
|
||||
import { GWEI, WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
|
||||
import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus';
|
||||
import { space } from 'lib/html-entities';
|
||||
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
@@ -22,12 +24,14 @@ import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
|
||||
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import PrevNext from 'ui/shared/PrevNext';
|
||||
import RawDataSnippet from 'ui/shared/RawDataSnippet';
|
||||
import StatusTag from 'ui/shared/statusTag/StatusTag';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
import Utilization from 'ui/shared/Utilization/Utilization';
|
||||
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
|
||||
@@ -184,6 +188,22 @@ const BlockDetails = ({ query }: Props) => {
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && !config.UI.views.block.hiddenFields?.batch && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Batch number"
|
||||
isLoading={ isPlaceholderData }
|
||||
>
|
||||
Batch
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.arbitrum.batch_number ?
|
||||
<BatchEntityL2 isLoading={ isPlaceholderData } number={ data.arbitrum.batch_number }/> :
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>Pending</Skeleton> }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
hint="Size of the block in bytes"
|
||||
isLoading={ isPlaceholderData }
|
||||
@@ -251,7 +271,9 @@ const BlockDetails = ({ query }: Props) => {
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && !config.UI.views.block.hiddenFields?.L1_status && (
|
||||
{ !config.UI.views.block.hiddenFields?.L1_status && rollupFeature.isEnabled &&
|
||||
((rollupFeature.type === 'zkSync' && data.zksync) || (rollupFeature.type === 'arbitrum' && data.arbitrum)) &&
|
||||
(
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Status is the short interpretation of the batch lifecycle"
|
||||
@@ -260,7 +282,16 @@ const BlockDetails = ({ query }: Props) => {
|
||||
Status
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isPlaceholderData }/>
|
||||
{ rollupFeature.type === 'zkSync' && data.zksync &&
|
||||
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isPlaceholderData }/> }
|
||||
{ rollupFeature.type === 'arbitrum' && data.arbitrum && (
|
||||
<VerificationSteps
|
||||
steps={ ARBITRUM_L2_TX_BATCH_STATUSES }
|
||||
currentStep={ data.arbitrum.status }
|
||||
currentStepPending={ getArbitrumVerificationStepStatus(data.arbitrum) === 'pending' }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
) }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
@@ -282,6 +313,42 @@ const BlockDetails = ({ query }: Props) => {
|
||||
</>
|
||||
) }
|
||||
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' &&
|
||||
(data.arbitrum?.commitment_transaction.hash || data.arbitrum?.confirmation_transaction.hash) &&
|
||||
(
|
||||
<>
|
||||
<DetailsInfoItemDivider/>
|
||||
{ data.arbitrum?.commitment_transaction.hash && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L1 transaction containing this batch commitment"
|
||||
isLoading={ isPlaceholderData }
|
||||
>
|
||||
Commitment tx
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<TxEntityL1 hash={ data.arbitrum?.commitment_transaction.hash } isLoading={ isPlaceholderData }/>
|
||||
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" ml={ 2 }/> }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
{ data.arbitrum?.confirmation_transaction.hash && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L1 transaction containing confirmation of this batch"
|
||||
isLoading={ isPlaceholderData }
|
||||
>
|
||||
Confirmation tx
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<TxEntityL1 hash={ data.arbitrum?.confirmation_transaction.hash } isLoading={ isPlaceholderData }/>
|
||||
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" ml={ 2 }/> }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
</>
|
||||
) }
|
||||
|
||||
{ !rollupFeature.isEnabled && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
|
||||
@@ -23,9 +23,11 @@ const TABS_HEIGHT = 88;
|
||||
interface Props {
|
||||
type?: BlockType;
|
||||
query: QueryWithPagesResult<'blocks'>;
|
||||
enableSocket?: boolean;
|
||||
top?: number;
|
||||
}
|
||||
|
||||
const BlocksContent = ({ type, query }: Props) => {
|
||||
const BlocksContent = ({ type, query, enableSocket = true, top }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const isMobile = useIsMobile();
|
||||
const [ socketAlert, setSocketAlert ] = React.useState('');
|
||||
@@ -71,7 +73,7 @@ const BlocksContent = ({ type, query }: Props) => {
|
||||
topic: 'blocks:new_block',
|
||||
onSocketClose: handleSocketClose,
|
||||
onSocketError: handleSocketError,
|
||||
isDisabled: query.isPlaceholderData || query.isError || query.pagination.page !== 1,
|
||||
isDisabled: query.isPlaceholderData || query.isError || query.pagination.page !== 1 || !enableSocket,
|
||||
});
|
||||
useSocketMessage({
|
||||
channel,
|
||||
@@ -82,7 +84,7 @@ const BlocksContent = ({ type, query }: Props) => {
|
||||
const content = query.data?.items ? (
|
||||
<>
|
||||
<Box display={{ base: 'block', lg: 'none' }}>
|
||||
{ query.pagination.page === 1 && (
|
||||
{ query.pagination.page === 1 && enableSocket && (
|
||||
<SocketNewItemsNotice.Mobile
|
||||
url={ window.location.href }
|
||||
num={ newItemsCount }
|
||||
@@ -96,10 +98,10 @@ const BlocksContent = ({ type, query }: Props) => {
|
||||
<Box display={{ base: 'none', lg: 'block' }}>
|
||||
<BlocksTable
|
||||
data={ query.data.items }
|
||||
top={ query.pagination.isVisible ? TABS_HEIGHT : 0 }
|
||||
top={ top || (query.pagination.isVisible ? TABS_HEIGHT : 0) }
|
||||
page={ query.pagination.page }
|
||||
isLoading={ query.isPlaceholderData }
|
||||
showSocketInfo={ query.pagination.page === 1 }
|
||||
showSocketInfo={ query.pagination.page === 1 && enableSocket }
|
||||
socketInfoNum={ newItemsCount }
|
||||
socketInfoAlert={ socketAlert }
|
||||
/>
|
||||
|
||||
@@ -70,7 +70,7 @@ const ZkEvmL2DepositsListItem = ({ item, isLoading }: Props) => {
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : (
|
||||
<chakra.span color="text_secondary">
|
||||
<chakra.span>
|
||||
Pending Claim
|
||||
</chakra.span>
|
||||
) }
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Hide, Show, Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { rightLineArrow, nbsp } from 'lib/html-entities';
|
||||
import { ARBITRUM_MESSAGES_ITEM } from 'stubs/arbitrumL2';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import ArbitrumL2MessagesListItem from 'ui/messages/ArbitrumL2MessagesListItem';
|
||||
import ArbitrumL2MessagesTable from 'ui/messages/ArbitrumL2MessagesTable';
|
||||
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
|
||||
import DataListDisplay from 'ui/shared/DataListDisplay';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText';
|
||||
|
||||
export type MessagesDirection = 'from-rollup' | 'to-rollup';
|
||||
|
||||
type Props = {
|
||||
direction: MessagesDirection;
|
||||
}
|
||||
|
||||
const ArbitrumL2Messages = ({ direction }: Props) => {
|
||||
const type = direction === 'from-rollup' ? 'withdrawals' : 'deposits';
|
||||
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
|
||||
resourceName: 'arbitrum_l2_messages',
|
||||
pathParams: { direction },
|
||||
options: {
|
||||
placeholderData: generateListStub<'arbitrum_l2_messages'>(
|
||||
ARBITRUM_MESSAGES_ITEM,
|
||||
50,
|
||||
{ next_page_params: { items_count: 50, direction: 'to-rollup', id: 123456 } },
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useApiQuery('arbitrum_l2_messages_count', {
|
||||
pathParams: { direction },
|
||||
queryOptions: {
|
||||
placeholderData: 1927029,
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map(((item, index) => (
|
||||
<ArbitrumL2MessagesListItem
|
||||
key={ String(item.id) + (isPlaceholderData ? index : '') }
|
||||
isLoading={ isPlaceholderData }
|
||||
item={ item }
|
||||
direction={ direction }
|
||||
/>
|
||||
))) }
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<ArbitrumL2MessagesTable
|
||||
items={ data.items }
|
||||
direction={ direction }
|
||||
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
</Hide>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const text = (() => {
|
||||
if (countersQuery.isError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
isLoaded={ !countersQuery.isPlaceholderData }
|
||||
display="inline-block"
|
||||
>
|
||||
A total of { countersQuery.data?.toLocaleString() } { type } found
|
||||
</Skeleton>
|
||||
);
|
||||
})();
|
||||
|
||||
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={ direction === 'from-rollup' ?
|
||||
`Withdrawals (L2${ nbsp }${ rightLineArrow }${ nbsp }L1)` :
|
||||
`Deposits (L1${ nbsp }${ rightLineArrow }${ nbsp }L2)` }
|
||||
withTextAd
|
||||
/>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
items={ data?.items }
|
||||
emptyText={ `There are no ${ type }.` }
|
||||
content={ content }
|
||||
actionBar={ actionBar }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2Messages;
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Skeleton, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
|
||||
import ArbitrumL2MessageStatus from 'ui/shared/statusTag/ArbitrumL2MessageStatus';
|
||||
|
||||
import type { MessagesDirection } from './ArbitrumL2Messages';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ArbitrumL2MessagesItem; isLoading?: boolean; direction: MessagesDirection };
|
||||
|
||||
const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'arbitrum') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeAgo = dayjs(item.origination_timestamp).fromNow();
|
||||
|
||||
const l1TxHash = direction === 'from-rollup' ? item.completion_transaction_hash : item.origination_transaction_hash;
|
||||
const l2TxHash = direction === 'from-rollup' ? item.origination_transaction_hash : item.completion_transaction_hash;
|
||||
|
||||
return (
|
||||
<ListItemMobileGrid.Container gridTemplateColumns="110px auto">
|
||||
|
||||
{ direction === 'to-rollup' && (
|
||||
<>
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 block</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BlockEntityL1
|
||||
number={ item.origination_transaction_block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ direction === 'from-rollup' && (
|
||||
<>
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>From</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<AddressEntity
|
||||
address={{ hash: item.origination_address }}
|
||||
truncation="constant"
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Message #</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ item.id }
|
||||
</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 transaction</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
{ l2TxHash ? (
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
hash={ l2TxHash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : (
|
||||
<chakra.span>
|
||||
N/A
|
||||
</chakra.span>
|
||||
) }
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">{ timeAgo }</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<ArbitrumL2MessageStatus status={ item.status } isLoading={ isLoading }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 transaction</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
{ l1TxHash ? (
|
||||
<TxEntityL1
|
||||
isLoading={ isLoading }
|
||||
hash={ l1TxHash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : (
|
||||
<chakra.span>
|
||||
N/A
|
||||
</chakra.span>
|
||||
) }
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
</ListItemMobileGrid.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2MessagesListItem;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
|
||||
import type { MessagesDirection } from './ArbitrumL2Messages';
|
||||
import ArbitrumL2MessagesTableItem from './ArbitrumL2MessagesTableItem';
|
||||
|
||||
type Props = {
|
||||
items: Array<ArbitrumL2MessagesItem>;
|
||||
direction: MessagesDirection;
|
||||
top: number;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ArbitrumL2MessagesTable = ({ items, direction, top, isLoading }: Props) => {
|
||||
return (
|
||||
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
{ direction === 'to-rollup' && <Th>L1 block</Th> }
|
||||
{ direction === 'from-rollup' && <Th>From</Th> }
|
||||
<Th>Message #</Th>
|
||||
<Th>L2 transaction</Th>
|
||||
<Th>Age</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>L1 transaction</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ items.map((item, index) => (
|
||||
<ArbitrumL2MessagesTableItem
|
||||
key={ String(item.id) + (isLoading ? index : '') }
|
||||
item={ item }
|
||||
direction={ direction }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2MessagesTable;
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Td, Tr, Skeleton, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import ArbitrumL2MessageStatus from 'ui/shared/statusTag/ArbitrumL2MessageStatus';
|
||||
|
||||
import type { MessagesDirection } from './ArbitrumL2Messages';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ArbitrumL2MessagesItem; isLoading?: boolean; direction: MessagesDirection };
|
||||
|
||||
const ArbitrumL2MessagesTableItem = ({ item, direction, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'arbitrum') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeAgo = dayjs(item.origination_timestamp).fromNow();
|
||||
|
||||
const l1TxHash = direction === 'from-rollup' ? item.completion_transaction_hash : item.origination_transaction_hash;
|
||||
const l2TxHash = direction === 'from-rollup' ? item.origination_transaction_hash : item.completion_transaction_hash;
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
{ direction === 'to-rollup' && (
|
||||
<Td verticalAlign="middle">
|
||||
<BlockEntityL1
|
||||
number={ item.origination_transaction_block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
noIcon
|
||||
/>
|
||||
</Td>
|
||||
) }
|
||||
{ direction === 'from-rollup' && (
|
||||
<Td verticalAlign="middle">
|
||||
<AddressEntity
|
||||
address={{ hash: item.origination_address }}
|
||||
truncation="constant"
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
/>
|
||||
</Td>
|
||||
) }
|
||||
<Td verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<span>{ item.id }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ l2TxHash ? (
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
hash={ l2TxHash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
/>
|
||||
) : (
|
||||
<chakra.span color="text_secondary">
|
||||
N/A
|
||||
</chakra.span>
|
||||
) }
|
||||
</Td>
|
||||
<Td verticalAlign="middle" pr={ 12 }>
|
||||
<Skeleton isLoaded={ !isLoading } color="text_secondary">
|
||||
<span>{ timeAgo }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<ArbitrumL2MessageStatus status={ item.status } isLoading={ isLoading }/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ l1TxHash ? (
|
||||
<TxEntityL1
|
||||
isLoading={ isLoading }
|
||||
hash={ l1TxHash }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
) : (
|
||||
<chakra.span color="text_secondary">
|
||||
N/A
|
||||
</chakra.span>
|
||||
) }
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2MessagesTableItem;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as depositsMock from 'mocks/arbitrum/deposits';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
import ArbitrumL2Deposits from './ArbitrumL2Deposits';
|
||||
|
||||
test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
|
||||
await mockTextAd();
|
||||
await mockEnvs(ENVS_MAP.arbitrumRollup);
|
||||
await mockApiResponse('arbitrum_l2_messages', depositsMock.baseResponse, { pathParams: { direction: 'to-rollup' } });
|
||||
await mockApiResponse('arbitrum_l2_messages_count', 3971111, { pathParams: { direction: 'to-rollup' } });
|
||||
|
||||
const component = await render(<ArbitrumL2Deposits/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import ArbitrumL2Messages from 'ui/messages/ArbitrumL2Messages';
|
||||
|
||||
const ArbitrumL2Deposits = () => {
|
||||
return <ArbitrumL2Messages direction="to-rollup"/>;
|
||||
};
|
||||
|
||||
export default ArbitrumL2Deposits;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import { batchData } from 'mocks/arbitrum/txnBatch';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect, devices } from 'playwright/lib';
|
||||
|
||||
import ArbitrumL2TxnBatch from './ArbitrumL2TxnBatch';
|
||||
|
||||
const batchNumber = '5';
|
||||
const hooksConfig = {
|
||||
router: {
|
||||
query: { number: batchNumber },
|
||||
},
|
||||
};
|
||||
|
||||
test.beforeEach(async({ mockTextAd, mockApiResponse, mockEnvs }) => {
|
||||
await mockEnvs(ENVS_MAP.arbitrumRollup);
|
||||
await mockTextAd();
|
||||
await mockApiResponse('arbitrum_l2_txn_batch', batchData, { pathParams: { number: batchNumber } });
|
||||
});
|
||||
|
||||
test('base view', async({ render }) => {
|
||||
const component = await render(<ArbitrumL2TxnBatch/>, { hooksConfig });
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
test('base view', async({ render }) => {
|
||||
const component = await render(<ArbitrumL2TxnBatch/>, { hooksConfig });
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { RoutedTab } from 'ui/shared/Tabs/types';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { useAppContext } from 'lib/contexts/app';
|
||||
import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError';
|
||||
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { ARBITRUM_L2_TXN_BATCH } from 'stubs/arbitrumL2';
|
||||
import { BLOCK } from 'stubs/block';
|
||||
import { TX } from 'stubs/tx';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import BlocksContent from 'ui/blocks/BlocksContent';
|
||||
import TextAd from 'ui/shared/ad/TextAd';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
import Pagination from 'ui/shared/pagination/Pagination';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
|
||||
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
|
||||
import ArbitrumL2TxnBatchDetails from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails';
|
||||
import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
|
||||
|
||||
const TAB_LIST_PROPS = {
|
||||
marginBottom: 0,
|
||||
py: 5,
|
||||
marginTop: -5,
|
||||
};
|
||||
|
||||
const TABS_HEIGHT = 80;
|
||||
|
||||
const ArbitrumL2TxnBatch = () => {
|
||||
const router = useRouter();
|
||||
const appProps = useAppContext();
|
||||
const number = getQueryParamString(router.query.number);
|
||||
const tab = getQueryParamString(router.query.tab);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const batchQuery = useApiQuery('arbitrum_l2_txn_batch', {
|
||||
pathParams: { number },
|
||||
queryOptions: {
|
||||
enabled: Boolean(number),
|
||||
placeholderData: ARBITRUM_L2_TXN_BATCH,
|
||||
},
|
||||
});
|
||||
|
||||
const batchTxsQuery = useQueryWithPages({
|
||||
resourceName: 'arbitrum_l2_txn_batch_txs',
|
||||
pathParams: { number },
|
||||
options: {
|
||||
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'),
|
||||
placeholderData: generateListStub<'arbitrum_l2_txn_batch_txs'>(TX, 50, { next_page_params: {
|
||||
batch_number: '8122',
|
||||
block_number: 1338932,
|
||||
index: 0,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
});
|
||||
|
||||
const batchBlocksQuery = useQueryWithPages({
|
||||
resourceName: 'arbitrum_l2_txn_batch_blocks',
|
||||
pathParams: { number },
|
||||
options: {
|
||||
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'),
|
||||
placeholderData: generateListStub<'arbitrum_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: {
|
||||
batch_number: '8122',
|
||||
block_number: 1338932,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
});
|
||||
|
||||
throwOnAbsentParamError(number);
|
||||
throwOnResourceLoadError(batchQuery);
|
||||
|
||||
let pagination;
|
||||
if (tab === 'txs') {
|
||||
pagination = batchTxsQuery.pagination;
|
||||
}
|
||||
if (tab === 'blocks') {
|
||||
pagination = batchBlocksQuery.pagination;
|
||||
}
|
||||
|
||||
const hasPagination = !isMobile && pagination?.isVisible;
|
||||
|
||||
const tabs: Array<RoutedTab> = React.useMemo(() => ([
|
||||
{ id: 'index', title: 'Details', component: <ArbitrumL2TxnBatchDetails query={ batchQuery }/> },
|
||||
{
|
||||
id: 'txs',
|
||||
title: 'Transactions',
|
||||
component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
|
||||
},
|
||||
{
|
||||
id: 'blocks',
|
||||
title: 'Blocks',
|
||||
component: <BlocksContent type="block" query={ batchBlocksQuery } enableSocket={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
|
||||
},
|
||||
].filter(Boolean)), [ batchQuery, batchTxsQuery, batchBlocksQuery, hasPagination ]);
|
||||
|
||||
const backLink = React.useMemo(() => {
|
||||
const hasGoBackLink = appProps.referrer && appProps.referrer.endsWith('/batches');
|
||||
|
||||
if (!hasGoBackLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Back to tx batches list',
|
||||
url: appProps.referrer,
|
||||
};
|
||||
}, [ appProps.referrer ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
<PageTitle
|
||||
title={ `Tx batch #${ number }` }
|
||||
backLink={ backLink }
|
||||
/>
|
||||
{ batchQuery.isPlaceholderData ?
|
||||
<TabsSkeleton tabs={ tabs }/> : (
|
||||
<RoutedTabs
|
||||
tabs={ tabs }
|
||||
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
|
||||
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
|
||||
stickyEnabled={ hasPagination }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2TxnBatch;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as arbitrumTxnBatchesMock from 'mocks/arbitrum/txnBatches';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
import ArbitrumL2TxnBatches from './ArbitrumL2TxnBatches';
|
||||
|
||||
test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
|
||||
test.slow();
|
||||
await mockEnvs(ENVS_MAP.arbitrumRollup);
|
||||
await mockTextAd();
|
||||
await mockApiResponse('arbitrum_l2_txn_batches', arbitrumTxnBatchesMock.baseResponse);
|
||||
await mockApiResponse('arbitrum_l2_txn_batches_count', 9927);
|
||||
|
||||
const component = await render(<ArbitrumL2TxnBatches/>);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Hide, Show, Skeleton, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { nbsp } from 'lib/html-entities';
|
||||
import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
|
||||
import DataListDisplay from 'ui/shared/DataListDisplay';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText';
|
||||
import ArbitrumL2TxnBatchesListItem from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchesListItem';
|
||||
import ArbitrumL2TxnBatchesTable from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchesTable';
|
||||
|
||||
const ArbitrumL2TxnBatches = () => {
|
||||
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
|
||||
resourceName: 'arbitrum_l2_txn_batches',
|
||||
options: {
|
||||
placeholderData: generateListStub<'arbitrum_l2_txn_batches'>(
|
||||
ARBITRUM_L2_TXN_BATCHES_ITEM,
|
||||
50,
|
||||
{
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
number: 9045200,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useApiQuery('arbitrum_l2_txn_batches_count', {
|
||||
queryOptions: {
|
||||
placeholderData: 5231746,
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map(((item, index) => (
|
||||
<ArbitrumL2TxnBatchesListItem
|
||||
key={ item.number + (isPlaceholderData ? String(index) : '') }
|
||||
item={ item }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
))) }
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<ArbitrumL2TxnBatchesTable items={ data.items } top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 } isLoading={ isPlaceholderData }/>
|
||||
</Hide>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const text = (() => {
|
||||
if (countersQuery.isError || isError || !data?.items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton isLoaded={ !countersQuery.isPlaceholderData && !isPlaceholderData } display="flex" flexWrap="wrap">
|
||||
Tx batch (L2 block)
|
||||
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].number } </Text>to
|
||||
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[data.items.length - 1].number } </Text>
|
||||
(total of { countersQuery.data?.toLocaleString() } batches)
|
||||
</Skeleton>
|
||||
);
|
||||
})();
|
||||
|
||||
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={ `Tx batches (L2${ nbsp }blocks)` } withTextAd/>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
items={ data?.items }
|
||||
emptyText="There are no tx batches."
|
||||
content={ content }
|
||||
actionBar={ actionBar }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2TxnBatches;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as depositsMock from 'mocks/arbitrum/withdrawals';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
import ArbitrumL2Withdrawals from './ArbitrumL2Withdrawals';
|
||||
|
||||
test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
|
||||
await mockTextAd();
|
||||
await mockEnvs(ENVS_MAP.arbitrumRollup);
|
||||
await mockApiResponse('arbitrum_l2_messages', depositsMock.baseResponse, { pathParams: { direction: 'from-rollup' } });
|
||||
await mockApiResponse('arbitrum_l2_messages_count', 3971111, { pathParams: { direction: 'from-rollup' } });
|
||||
|
||||
const component = await render(<ArbitrumL2Withdrawals/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import ArbitrumL2Messages from 'ui/messages/ArbitrumL2Messages';
|
||||
|
||||
const ArbitrumL2Withdrawals = () => {
|
||||
return <ArbitrumL2Messages direction="from-rollup"/>;
|
||||
};
|
||||
|
||||
export default ArbitrumL2Withdrawals;
|
||||
@@ -47,6 +47,11 @@ const BlockPageContent = () => {
|
||||
const blockWithdrawalsQuery = useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab });
|
||||
const blockBlobTxsQuery = useBlockBlobTxsQuery({ heightOrHash, blockQuery, tab });
|
||||
|
||||
const hasPagination = !isMobile && (
|
||||
(tab === 'txs' && blockTxsQuery.pagination.isVisible) ||
|
||||
(tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible)
|
||||
);
|
||||
|
||||
const tabs: Array<RoutedTab> = React.useMemo(() => ([
|
||||
{
|
||||
id: 'index',
|
||||
@@ -64,7 +69,7 @@ const BlockPageContent = () => {
|
||||
component: (
|
||||
<>
|
||||
{ blockTxsQuery.isDegradedData && <ServiceDegradationWarning isLoading={ blockTxsQuery.isPlaceholderData } mb={ 6 }/> }
|
||||
<TxsWithFrontendSorting query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false } top={ TABS_HEIGHT }/>
|
||||
<TxsWithFrontendSorting query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -87,12 +92,7 @@ const BlockPageContent = () => {
|
||||
</>
|
||||
),
|
||||
} : null,
|
||||
].filter(Boolean)), [ blockBlobTxsQuery, blockQuery, blockTxsQuery, blockWithdrawalsQuery ]);
|
||||
|
||||
const hasPagination = !isMobile && (
|
||||
(tab === 'txs' && blockTxsQuery.pagination.isVisible) ||
|
||||
(tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible)
|
||||
);
|
||||
].filter(Boolean)), [ blockBlobTxsQuery, blockQuery, blockTxsQuery, blockWithdrawalsQuery, hasPagination ]);
|
||||
|
||||
let pagination;
|
||||
if (tab === 'txs') {
|
||||
|
||||
@@ -27,6 +27,8 @@ const TAB_LIST_PROPS = {
|
||||
marginTop: -5,
|
||||
};
|
||||
|
||||
const TABS_HEIGHT = 80;
|
||||
|
||||
const ZkSyncL2TxnBatch = () => {
|
||||
const router = useRouter();
|
||||
const appProps = useAppContext();
|
||||
@@ -59,10 +61,16 @@ const ZkSyncL2TxnBatch = () => {
|
||||
throwOnAbsentParamError(number);
|
||||
throwOnResourceLoadError(batchQuery);
|
||||
|
||||
const hasPagination = !isMobile && batchTxsQuery.pagination.isVisible && tab === 'txs';
|
||||
|
||||
const tabs: Array<RoutedTab> = React.useMemo(() => ([
|
||||
{ id: 'index', title: 'Details', component: <ZkSyncL2TxnBatchDetails query={ batchQuery }/> },
|
||||
{ id: 'txs', title: 'Transactions', component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false }/> },
|
||||
].filter(Boolean)), [ batchQuery, batchTxsQuery ]);
|
||||
{
|
||||
id: 'txs',
|
||||
title: 'Transactions',
|
||||
component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
|
||||
},
|
||||
].filter(Boolean)), [ batchQuery, batchTxsQuery, hasPagination ]);
|
||||
|
||||
const backLink = React.useMemo(() => {
|
||||
const hasGoBackLink = appProps.referrer && appProps.referrer.endsWith('/batches');
|
||||
@@ -77,8 +85,6 @@ const ZkSyncL2TxnBatch = () => {
|
||||
};
|
||||
}, [ appProps.referrer ]);
|
||||
|
||||
const hasPagination = !isMobile && batchTxsQuery.pagination.isVisible && tab === 'txs';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import type { StatusTagType } from './StatusTag';
|
||||
import StatusTag from './StatusTag';
|
||||
|
||||
export interface Props {
|
||||
status: ArbitrumL2MessagesItem['status'];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ArbitrumL2MessageStatus = ({ status, isLoading }: Props) => {
|
||||
let type: StatusTagType;
|
||||
|
||||
switch (status) {
|
||||
case 'relayed':
|
||||
case 'confirmed':
|
||||
type = 'ok';
|
||||
break;
|
||||
default:
|
||||
type = 'pending';
|
||||
break;
|
||||
}
|
||||
|
||||
return <StatusTag type={ type } text={ status } isLoading={ isLoading }/>;
|
||||
};
|
||||
|
||||
export default ArbitrumL2MessageStatus;
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import type { StatusTagType } from './StatusTag';
|
||||
import StatusTag from './StatusTag';
|
||||
|
||||
export interface Props {
|
||||
status: ArbitrumL2TxnBatchesItem['commitment_transaction']['status'];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ArbitrumL2TxnBatchStatus = ({ status, isLoading }: Props) => {
|
||||
let type: StatusTagType;
|
||||
|
||||
switch (status) {
|
||||
case 'finalized':
|
||||
type = 'ok';
|
||||
break;
|
||||
default:
|
||||
type = 'pending';
|
||||
break;
|
||||
}
|
||||
|
||||
return <StatusTag type={ type } text={ status } isLoading={ isLoading }/>;
|
||||
};
|
||||
|
||||
export default ArbitrumL2TxnBatchStatus;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TagLabel, Tooltip } from '@chakra-ui/react';
|
||||
import { TagLabel, Tooltip, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
@@ -12,12 +12,15 @@ export interface Props {
|
||||
text: string;
|
||||
errorText?: string | null;
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const StatusTag = ({ type, text, errorText, isLoading }: Props) => {
|
||||
const StatusTag = ({ type, text, errorText, isLoading, className }: Props) => {
|
||||
let icon: IconName;
|
||||
let colorScheme;
|
||||
|
||||
const capitalizedText = text.charAt(0).toUpperCase() + text.slice(1);
|
||||
|
||||
switch (type) {
|
||||
case 'ok':
|
||||
icon = 'status/success';
|
||||
@@ -35,12 +38,12 @@ const StatusTag = ({ type, text, errorText, isLoading }: Props) => {
|
||||
|
||||
return (
|
||||
<Tooltip label={ errorText }>
|
||||
<Tag colorScheme={ colorScheme } display="flex" isLoading={ isLoading } >
|
||||
<Tag colorScheme={ colorScheme } display="flex" isLoading={ isLoading } className={ className }>
|
||||
<IconSvg boxSize={ 2.5 } name={ icon } mr={ 1 } flexShrink={ 0 }/>
|
||||
<TagLabel display="block">{ text }</TagLabel>
|
||||
<TagLabel display="block">{ capitalizedText }</TagLabel>
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusTag;
|
||||
export default chakra(StatusTag);
|
||||
|
||||
@@ -9,10 +9,16 @@ type Props = {
|
||||
step: Step;
|
||||
isLast: boolean;
|
||||
isPassed: boolean;
|
||||
isPending?: boolean;
|
||||
}
|
||||
|
||||
const VerificationStep = ({ step, isLast, isPassed }: Props) => {
|
||||
const stepColor = isPassed ? 'green.500' : 'text_secondary';
|
||||
const VerificationStep = ({ step, isLast, isPassed, isPending }: Props) => {
|
||||
let stepColor = 'text_secondary';
|
||||
if (isPending) {
|
||||
stepColor = 'yellow.500';
|
||||
} else if (isPassed) {
|
||||
stepColor = 'green.500';
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack gap={ 2 } color={ stepColor }>
|
||||
|
||||
@@ -7,13 +7,14 @@ import VerificationStep from './VerificationStep';
|
||||
|
||||
export interface Props {
|
||||
currentStep: string;
|
||||
currentStepPending?: boolean;
|
||||
steps: Array<Step>;
|
||||
isLoading?: boolean;
|
||||
rightSlot?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const VerificationSteps = ({ currentStep, steps, isLoading, rightSlot, className }: Props) => {
|
||||
const VerificationSteps = ({ currentStep, currentStepPending, steps, isLoading, rightSlot, className }: Props) => {
|
||||
const currentStepIndex = steps.findIndex((step) => {
|
||||
const label = typeof step === 'string' ? step : step.label;
|
||||
return label === currentStep;
|
||||
@@ -34,6 +35,7 @@ const VerificationSteps = ({ currentStep, steps, isLoading, rightSlot, className
|
||||
step={ step }
|
||||
isLast={ index === steps.length - 1 && !rightSlot }
|
||||
isPassed={ index <= currentStepIndex }
|
||||
isPending={ index === currentStepIndex && currentStepPending }
|
||||
/>
|
||||
)) }
|
||||
{ rightSlot }
|
||||
|
||||
@@ -15,6 +15,7 @@ import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
import { scroller, Element } from 'react-scroll';
|
||||
|
||||
import { ARBITRUM_L2_TX_BATCH_STATUSES } from 'types/api/arbitrumL2';
|
||||
import type { Transaction } from 'types/api/transaction';
|
||||
import { ZKEVM_L2_TX_STATUSES } from 'types/api/transaction';
|
||||
import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2';
|
||||
@@ -23,6 +24,7 @@ import { route } from 'nextjs-routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { WEI, WEI_IN_GWEI } from 'lib/consts';
|
||||
import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus';
|
||||
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
|
||||
import getConfirmationDuration from 'lib/tx/getConfirmationDuration';
|
||||
import { currencyUnits } from 'lib/units';
|
||||
@@ -36,10 +38,12 @@ import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
|
||||
import RawInputData from 'ui/shared/RawInputData';
|
||||
import StatusTag from 'ui/shared/statusTag/StatusTag';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
|
||||
@@ -152,7 +156,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
{
|
||||
rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync') ?
|
||||
rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum') ?
|
||||
'L2 status and method' :
|
||||
'Status and method'
|
||||
}
|
||||
@@ -164,6 +168,11 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
{ data.method }
|
||||
</Tag>
|
||||
) }
|
||||
{ data.arbitrum?.contains_message && (
|
||||
<Tag isLoading={ isLoading } isTruncated ml={ 3 }>
|
||||
{ data.arbitrum?.contains_message === 'incoming' ? 'Incoming message' : 'Outgoing message' }
|
||||
</Tag>
|
||||
) }
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.op_withdrawals && data.op_withdrawals.length > 0 &&
|
||||
@@ -172,7 +181,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
<DetailsInfoItem.Label
|
||||
hint="Detailed status progress of the transaction"
|
||||
>
|
||||
Withdrawal status
|
||||
Withdrawal status
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Flex flexDir="column" rowGap={ 2 }>
|
||||
@@ -207,6 +216,25 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.arbitrum?.status && !config.UI.views.tx.hiddenFields?.L1_status && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Status of the transaction confirmation path to L1"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 status
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<VerificationSteps
|
||||
currentStep={ data.arbitrum.status }
|
||||
currentStepPending={ getArbitrumVerificationStepStatus(data.arbitrum) === 'pending' }
|
||||
steps={ ARBITRUM_L2_TX_BATCH_STATUSES }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.revert_reason && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
@@ -226,7 +254,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
hint="Status is the short interpretation of the batch lifecycle"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 status
|
||||
L1 status
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isLoading }/>
|
||||
@@ -295,6 +323,22 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.arbitrum && !config.UI.views.tx.hiddenFields?.batch && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Index of the batch containing this transaction"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
Batch
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.arbitrum.batch_number ?
|
||||
<BatchEntityL2 isLoading={ isLoading } number={ data.arbitrum.batch_number }/> :
|
||||
<Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.timestamp && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
@@ -412,6 +456,41 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
|
||||
<DetailsInfoItemDivider/>
|
||||
|
||||
{ (data.arbitrum?.commitment_transaction.hash || data.arbitrum?.confirmation_transaction.hash) &&
|
||||
(
|
||||
<>
|
||||
{ data.arbitrum?.commitment_transaction.hash && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L1 transaction containing this batch commitment"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
Commitment tx
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<TxEntityL1 hash={ data.arbitrum?.commitment_transaction.hash } isLoading={ isLoading }/>
|
||||
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" ml={ 2 }/> }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
{ data.arbitrum?.confirmation_transaction.hash && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L1 transaction containing confirmation of this batch"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
Confirmation tx
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<TxEntityL1 hash={ data.arbitrum?.confirmation_transaction.hash } isLoading={ isLoading }/>
|
||||
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" ml={ 2 }/> }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
<DetailsInfoItemDivider/>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.zkevm_sequence_hash && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import { Grid, Skeleton } from '@chakra-ui/react';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
|
||||
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
|
||||
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
|
||||
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import PrevNext from 'ui/shared/PrevNext';
|
||||
|
||||
interface Props {
|
||||
query: UseQueryResult<ArbitrumL2TxnBatch, ResourceError>;
|
||||
}
|
||||
|
||||
const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { data, isPlaceholderData, isError, error } = query;
|
||||
|
||||
const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const increment = direction === 'next' ? +1 : -1;
|
||||
const nextId = String(data.number + increment);
|
||||
|
||||
router.push({ pathname: '/batches/[number]', query: { number: nextId } }, undefined);
|
||||
}, [ data, router ]);
|
||||
|
||||
if (isError) {
|
||||
if (isCustomAppError(error)) {
|
||||
throwOnResourceLoadError({ isError, error });
|
||||
}
|
||||
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blocksCount = data.end_block - data.start_block + 1;
|
||||
|
||||
return (
|
||||
<Grid
|
||||
columnGap={ 8 }
|
||||
rowGap={{ base: 3, lg: 3 }}
|
||||
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
|
||||
overflow="hidden"
|
||||
>
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Batch number indicates the length of batches produced by grouping L2 blocks to be proven on L1"
|
||||
>
|
||||
Tx batch number
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>
|
||||
{ data.number }
|
||||
</Skeleton>
|
||||
<PrevNext
|
||||
ml={ 6 }
|
||||
onClick={ handlePrevNextClick }
|
||||
prevLabel="View previous tx batch"
|
||||
nextLabel="View next tx batch"
|
||||
isPrevDisabled={ data.number === 0 }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Date and time at which batch is submitted to L1"
|
||||
>
|
||||
Timestamp
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.commitment_transaction.timestamp ?
|
||||
<DetailsTimestamp timestamp={ data.commitment_transaction.timestamp }isLoading={ isPlaceholderData }/> :
|
||||
'Undefined'
|
||||
}
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Number of transactions in this batch"
|
||||
>
|
||||
Transactions
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>
|
||||
<LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }>
|
||||
{ data.transactions_count } transaction{ data.transactions_count === 1 ? '' : 's' }
|
||||
</LinkInternal>
|
||||
</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Number of L2 blocks in this batch"
|
||||
>
|
||||
Blocks
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>
|
||||
<LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'blocks' } }) }>
|
||||
{ blocksCount } block{ blocksCount === 1 ? '' : 's' }
|
||||
</LinkInternal>
|
||||
</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Hash of L1 transaction in which transactions was committed"
|
||||
>
|
||||
L1 transaction hash
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<TxEntityL1
|
||||
isLoading={ isPlaceholderData }
|
||||
hash={ data.commitment_transaction.hash }
|
||||
maxW="100%"
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Heigh of L1 block which includes L1 transactions"
|
||||
>
|
||||
L1 block
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<BlockEntityL1
|
||||
isLoading={ isPlaceholderData }
|
||||
number={ data.commitment_transaction.block_number }
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="The hash of the state before the batch"
|
||||
>
|
||||
Before acc
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
|
||||
<HashStringShortenDynamic hash={ data.before_acc }/>
|
||||
<CopyToClipboard text={ data.before_acc }/>
|
||||
</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="The hash of the state after the batch"
|
||||
>
|
||||
After acc
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
|
||||
<HashStringShortenDynamic hash={ data.after_acc }/>
|
||||
<CopyToClipboard text={ data.after_acc }/>
|
||||
</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2TxnBatchDetails;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
|
||||
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
|
||||
import ArbitrumL2TxnBatchStatus from 'ui/shared/statusTag/ArbitrumL2TxnBatchStatus';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ArbitrumL2TxnBatchesItem; isLoading?: boolean };
|
||||
|
||||
const ArbitrumL2TxnBatchesListItem = ({ item, isLoading }: Props) => {
|
||||
const timeAgo = item.commitment_transaction.timestamp ? dayjs(item.commitment_transaction.timestamp).fromNow() : 'Undefined';
|
||||
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'arbitrum') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItemMobileGrid.Container gridTemplateColumns="110px auto">
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Batch #</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BatchEntityL2
|
||||
isLoading={ isLoading }
|
||||
number={ item.number }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 status</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<ArbitrumL2TxnBatchStatus status={ item.commitment_transaction.status } isLoading={ isLoading }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 block</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BlockEntityL1
|
||||
number={ item.commitment_transaction.block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
{ item.block_count && (
|
||||
<>
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 block txn count</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.block_count }</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 transaction</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TxEntityL1
|
||||
hash={ item.commitment_transaction.hash }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">{ timeAgo }</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn count</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) }
|
||||
isLoading={ isLoading }
|
||||
fontWeight={ 600 }
|
||||
>
|
||||
<Skeleton isLoaded={ !isLoading } minW="40px">
|
||||
{ item.transactions_count }
|
||||
</Skeleton>
|
||||
</LinkInternal>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
</ListItemMobileGrid.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2TxnBatchesListItem;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
|
||||
import ArbitrumL2TxnBatchesTableItem from './ArbitrumL2TxnBatchesTableItem';
|
||||
|
||||
type Props = {
|
||||
items: Array<ArbitrumL2TxnBatchesItem>;
|
||||
top: number;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ArbitrumL2TxnBatchesTable = ({ items, top, isLoading }: Props) => {
|
||||
return (
|
||||
<Table variant="simple" size="sm" minW="1000px" style={{ tableLayout: 'auto' }}>
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th>Batch #</Th>
|
||||
<Th>L1 status</Th>
|
||||
<Th>L1 block</Th>
|
||||
<Th>L2 block txn count</Th>
|
||||
<Th>L1 transaction</Th>
|
||||
<Th>Age</Th>
|
||||
<Th>Txn count</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ items.map((item, index) => (
|
||||
<ArbitrumL2TxnBatchesTableItem
|
||||
key={ item.number + (isLoading ? String(index) : '') }
|
||||
item={ item }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArbitrumL2TxnBatchesTable;
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Td, Tr, Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
|
||||
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import ArbitrumL2TxnBatchStatus from 'ui/shared/statusTag/ArbitrumL2TxnBatchStatus';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ArbitrumL2TxnBatchesItem; isLoading?: boolean };
|
||||
|
||||
const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
|
||||
const timeAgo = item.commitment_transaction.timestamp ? dayjs(item.commitment_transaction.timestamp).fromNow() : 'Undefined';
|
||||
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'arbitrum') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td verticalAlign="middle">
|
||||
<BatchEntityL2
|
||||
isLoading={ isLoading }
|
||||
number={ item.number }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
noIcon
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<ArbitrumL2TxnBatchStatus status={ item.commitment_transaction.status } isLoading={ isLoading }/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<BlockEntityL1
|
||||
number={ item.commitment_transaction.block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.block_count || 'N/A' }</Skeleton>
|
||||
</Td>
|
||||
<Td pr={ 12 } verticalAlign="middle">
|
||||
<TxEntityL1
|
||||
hash={ item.commitment_transaction.hash }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading } color="text_secondary">
|
||||
<span>{ timeAgo }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) }
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
<Skeleton isLoaded={ !isLoading } minW="40px">
|
||||
{ item.transactions_count }
|
||||
</Skeleton>
|
||||
</LinkInternal>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default TxnBatchesTableItem;
|
||||
@@ -70,7 +70,7 @@ const ZkEvmL2WithdrawalsListItem = ({ item, isLoading }: Props) => {
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : (
|
||||
<chakra.span color="text_secondary">
|
||||
<chakra.span>
|
||||
Pending Claim
|
||||
</chakra.span>
|
||||
) }
|
||||
|
||||