@@ -25,6 +25,7 @@ on:
|
||||
- optimism_sepolia
|
||||
- polygon
|
||||
- rootstock
|
||||
- scroll_sepolia
|
||||
- shibarium
|
||||
- stability
|
||||
- zkevm
|
||||
|
||||
@@ -26,6 +26,7 @@ on:
|
||||
- polygon
|
||||
- rootstock
|
||||
- shibarium
|
||||
- scroll_sepolia
|
||||
- stability
|
||||
- zkevm
|
||||
- zilliqa_prototestnet
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Set of ENVs for Scroll Sepolia Testnet network explorer
|
||||
# https://scroll-sepolia.blockscout.com
|
||||
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=scroll_sepolia"
|
||||
|
||||
# Local ENVs
|
||||
NEXT_PUBLIC_APP_PROTOCOL=http
|
||||
NEXT_PUBLIC_APP_HOST=localhost
|
||||
NEXT_PUBLIC_APP_PORT=3000
|
||||
NEXT_PUBLIC_APP_ENV=development
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
|
||||
|
||||
# Instance ENVs
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_HOST=scroll-sepolia.blockscout.com
|
||||
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
|
||||
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'}]
|
||||
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/scroll-testnet.json
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xa0d22caf6217a488b1e97b646c5ed88e8a3020a607bcd1f3fe8d4c430bb19ad5
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(255, 238, 218, 1)'],'text_color':['rgba(25, 6, 2, 1)']}
|
||||
NEXT_PUBLIC_IS_TESTNET=true
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
|
||||
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/scroll.svg
|
||||
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/scroll-dark.svg
|
||||
NEXT_PUBLIC_NETWORK_ID=534351
|
||||
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/scroll.svg
|
||||
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/scroll-dark.svg
|
||||
NEXT_PUBLIC_NETWORK_NAME=Scroll Sepolia Testnet
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia-rpc.scroll.io
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME=Scroll Sepolia Testnet
|
||||
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
|
||||
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/scroll-testnet.png
|
||||
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=6Ld0iT8aAAAAAJdju0CmAwGjW7JTDvIw-Q5pwt5T
|
||||
NEXT_PUBLIC_STATS_API_HOST=https://stats-scroll-sepolia.k8s-prod-2.blockscout.com
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_ROLLUP_TYPE=scroll
|
||||
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/
|
||||
@@ -436,7 +436,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' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' \| 'scroll'` | 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 (Optimistic stack only) | Required 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 (Optimistic stack only) | - | - | `true` | v1.31.0+ |
|
||||
|
||||
@@ -105,6 +105,13 @@ import type {
|
||||
RewardsUserDailyClaimResponse,
|
||||
RewardsUserReferralsResponse,
|
||||
} from 'types/api/rewards';
|
||||
import type {
|
||||
ScrollL2BatchesResponse,
|
||||
ScrollL2TxnBatch,
|
||||
ScrollL2TxnBatchTxs,
|
||||
ScrollL2TxnBatchBlocks,
|
||||
ScrollL2MessagesResponse,
|
||||
} from 'types/api/scrollL2';
|
||||
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
|
||||
import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium';
|
||||
import type { HomeStats } from 'types/api/stats';
|
||||
@@ -983,6 +990,51 @@ export const RESOURCES = {
|
||||
path: '/api/v2/shibarium/withdrawals/count',
|
||||
},
|
||||
|
||||
// SCROLL L2
|
||||
scroll_l2_deposits: {
|
||||
path: '/api/v2/scroll/deposits',
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
scroll_l2_deposits_count: {
|
||||
path: '/api/v2/scroll/deposits/count',
|
||||
},
|
||||
|
||||
scroll_l2_withdrawals: {
|
||||
path: '/api/v2/scroll/withdrawals',
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
scroll_l2_withdrawals_count: {
|
||||
path: '/api/v2/scroll/withdrawals/count',
|
||||
},
|
||||
|
||||
scroll_l2_txn_batches: {
|
||||
path: '/api/v2/scroll/batches',
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
scroll_l2_txn_batches_count: {
|
||||
path: '/api/v2/scroll/batches/count',
|
||||
},
|
||||
|
||||
scroll_l2_txn_batch: {
|
||||
path: '/api/v2/scroll/batches/:number',
|
||||
pathParams: [ 'number' as const ],
|
||||
},
|
||||
|
||||
scroll_l2_txn_batch_txs: {
|
||||
path: '/api/v2/transactions/scroll-batch/:number',
|
||||
pathParams: [ 'number' as const ],
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
scroll_l2_txn_batch_blocks: {
|
||||
path: '/api/v2/blocks/scroll-batch/:number',
|
||||
pathParams: [ 'number' as const ],
|
||||
filterFields: [],
|
||||
},
|
||||
|
||||
// NOVES-FI
|
||||
noves_transaction: {
|
||||
path: '/api/v2/proxy/noves-fi/transactions/:hash',
|
||||
@@ -1130,7 +1182,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
|
||||
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
|
||||
'watchlist' | 'private_tags_address' | 'private_tags_tx' |
|
||||
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history' |
|
||||
'token_transfers_all';
|
||||
'token_transfers_all' | 'scroll_l2_txn_batches' | 'scroll_l2_txn_batch_txs' | 'scroll_l2_txn_batch_blocks' |
|
||||
'scroll_l2_deposits' | 'scroll_l2_withdrawals';
|
||||
|
||||
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
|
||||
|
||||
@@ -1278,6 +1331,8 @@ Q extends 'zksync_l2_txn_batches' ? ZkSyncBatchesResponse :
|
||||
Q extends 'zksync_l2_txn_batches_count' ? number :
|
||||
Q extends 'zksync_l2_txn_batch' ? ZkSyncBatch :
|
||||
Q extends 'zksync_l2_txn_batch_txs' ? ZkSyncBatchTxs :
|
||||
Q extends 'scroll_l2_txn_batch_txs' ? ScrollL2TxnBatchTxs :
|
||||
Q extends 'scroll_l2_txn_batch_blocks' ? ScrollL2TxnBatchBlocks :
|
||||
Q extends 'contract_security_audits' ? SmartContractSecurityAudits :
|
||||
Q extends 'addresses_lookup' ? bens.LookupAddressResponse :
|
||||
Q extends 'address_domain' ? bens.GetAddressResponse :
|
||||
@@ -1313,6 +1368,13 @@ Q extends 'rewards_user_daily_claim' ? RewardsUserDailyClaimResponse :
|
||||
Q extends 'rewards_user_referrals' ? RewardsUserReferralsResponse :
|
||||
Q extends 'token_transfers_all' ? TokenTransferResponse :
|
||||
Q extends 'address_xstar_score' ? AddressXStarResponse :
|
||||
Q extends 'scroll_l2_txn_batches' ? ScrollL2BatchesResponse :
|
||||
Q extends 'scroll_l2_txn_batches_count' ? number :
|
||||
Q extends 'scroll_l2_txn_batch' ? ScrollL2TxnBatch :
|
||||
Q extends 'scroll_l2_deposits' ? ScrollL2MessagesResponse :
|
||||
Q extends 'scroll_l2_deposits_count' ? number :
|
||||
Q extends 'scroll_l2_withdrawals' ? ScrollL2MessagesResponse :
|
||||
Q extends 'scroll_l2_withdrawals_count' ? number :
|
||||
never;
|
||||
/* eslint-enable @stylistic/indent */
|
||||
|
||||
|
||||
@@ -109,7 +109,12 @@ export default function useNavItems(): ReturnType {
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
if (rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum' || rollupFeature.type === 'zkEvm')) {
|
||||
if (rollupFeature.isEnabled && (
|
||||
rollupFeature.type === 'optimistic' ||
|
||||
rollupFeature.type === 'arbitrum' ||
|
||||
rollupFeature.type === 'zkEvm' ||
|
||||
rollupFeature.type === 'scroll'
|
||||
)) {
|
||||
blockchainNavItems = [
|
||||
[
|
||||
txs,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { ScrollL2MessagesResponse } from 'types/api/scrollL2';
|
||||
|
||||
export const baseResponse: ScrollL2MessagesResponse = {
|
||||
items: [
|
||||
{
|
||||
id: 930795,
|
||||
origination_transaction_block_number: 20639178,
|
||||
origination_transaction_hash: '0x70380f2c6ecd53aa6e0608e6c9d770acaa29c0508869ec296bae3e09678ea9f4',
|
||||
origination_timestamp: '2024-08-30T05:03:23.000000Z',
|
||||
completion_transaction_hash: null,
|
||||
value: '5084131319054877748',
|
||||
},
|
||||
{
|
||||
id: 930748,
|
||||
origination_transaction_block_number: 20638104,
|
||||
origination_transaction_hash: '0x7e7b4d5ff0b7a6af5e52f4aa2ad9eca3c0c5664368cbb781e04b5b13c6109b2b',
|
||||
origination_timestamp: '2024-08-30T01:26:35.000000Z',
|
||||
completion_transaction_hash: '0x426b16ea3a42228f6d754ae55c348986122cdb1e4331b6fd454975776f513ea1',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
id: 1,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { ScrollL2BatchesResponse } from 'types/api/scrollL2';
|
||||
|
||||
export const batchData = {
|
||||
number: 66928,
|
||||
commitment_transaction: {
|
||||
block_number: 19114878,
|
||||
hash: '0x57552c0dbcf56383ee2efdf8fd6be143b355135fc300361924582c308877b8b7',
|
||||
timestamp: '2024-01-29T21:31:35.000000Z',
|
||||
},
|
||||
confirmation_transaction: {
|
||||
block_number: null,
|
||||
hash: null,
|
||||
timestamp: null,
|
||||
},
|
||||
data_availability: {
|
||||
batch_data_container: 'in_blob4844' as const,
|
||||
},
|
||||
start_block: 456000,
|
||||
end_block: 789000,
|
||||
transaction_count: 654,
|
||||
};
|
||||
|
||||
export const baseResponse: ScrollL2BatchesResponse = {
|
||||
items: [
|
||||
batchData,
|
||||
{
|
||||
number: 66879,
|
||||
commitment_transaction: {
|
||||
block_number: 19114386,
|
||||
hash: '0x0d33245814b9e61c8f0ed6fd3fb7464f34be33d2c3aee69629d65e8995d77edc',
|
||||
timestamp: '2024-01-29T19:52:35.000000Z',
|
||||
},
|
||||
confirmation_transaction: {
|
||||
block_number: 19114558,
|
||||
hash: '0x6f9a19d503947ec91d6e9d5c2129913a7def86fd0f87061c06e5994cf857bee0',
|
||||
timestamp: '2024-01-29T20:27:11.000000Z',
|
||||
},
|
||||
data_availability: {
|
||||
batch_data_container: 'in_calldata',
|
||||
},
|
||||
start_block: 456000,
|
||||
end_block: 789000,
|
||||
transaction_count: 962,
|
||||
},
|
||||
],
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
number: 1,
|
||||
},
|
||||
};
|
||||
@@ -67,7 +67,7 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
|
||||
return account(context);
|
||||
};
|
||||
|
||||
const DEPOSITS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum' ];
|
||||
const DEPOSITS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
|
||||
export const deposits: GetServerSideProps<Props> = async(context) => {
|
||||
if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) {
|
||||
return {
|
||||
@@ -78,7 +78,7 @@ export const deposits: GetServerSideProps<Props> = async(context) => {
|
||||
return base(context);
|
||||
};
|
||||
|
||||
const WITHDRAWALS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum' ];
|
||||
const WITHDRAWALS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
|
||||
export const withdrawals: GetServerSideProps<Props> = async(context) => {
|
||||
if (
|
||||
!config.features.beaconChain.isEnabled &&
|
||||
@@ -112,7 +112,7 @@ export const outputRoots: GetServerSideProps<Props> = async(context) => {
|
||||
return base(context);
|
||||
};
|
||||
|
||||
const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic' ];
|
||||
const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic', 'scroll' ];
|
||||
export const batch: GetServerSideProps<Props> = async(context) => {
|
||||
if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) {
|
||||
return {
|
||||
|
||||
@@ -23,6 +23,8 @@ const Batch = dynamic(() => {
|
||||
return import('ui/pages/ZkEvmL2TxnBatch');
|
||||
case 'zkSync':
|
||||
return import('ui/pages/ZkSyncL2TxnBatch');
|
||||
case 'scroll':
|
||||
return import('ui/pages/ScrollL2TxnBatch');
|
||||
}
|
||||
throw new Error('Txn batches feature is not enabled.');
|
||||
}, { ssr: false });
|
||||
|
||||
@@ -21,6 +21,8 @@ const Batches = dynamic(() => {
|
||||
return import('ui/pages/OptimisticL2TxnBatches');
|
||||
case 'arbitrum':
|
||||
return import('ui/pages/ArbitrumL2TxnBatches');
|
||||
case 'scroll':
|
||||
return import('ui/pages/ScrollL2TxnBatches');
|
||||
}
|
||||
throw new Error('Txn batches feature is not enabled.');
|
||||
}, { ssr: false });
|
||||
|
||||
@@ -24,6 +24,10 @@ const Deposits = dynamic(() => {
|
||||
return import('ui/pages/ZkEvmL2Deposits');
|
||||
}
|
||||
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'scroll') {
|
||||
return import('ui/pages/ScrollL2Deposits');
|
||||
}
|
||||
|
||||
throw new Error('Deposits feature is not enabled.');
|
||||
}, { ssr: false });
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ const Withdrawals = dynamic(() => {
|
||||
return import('ui/pages/ZkEvmL2Withdrawals');
|
||||
}
|
||||
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'scroll') {
|
||||
return import('ui/pages/ScrollL2Withdrawals');
|
||||
}
|
||||
|
||||
if (beaconChainFeature.isEnabled) {
|
||||
return import('ui/pages/BeaconChainWithdrawals');
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
|
||||
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
|
||||
[ 'NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS', 'none' ],
|
||||
],
|
||||
scrollRollup: [
|
||||
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'scroll' ],
|
||||
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
|
||||
],
|
||||
bridgedTokens: [
|
||||
[ 'NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS', '[{"id":"1","title":"Ethereum","short_title":"ETH","base_url":"https://eth.blockscout.com/token/"},{"id":"56","title":"Binance Smart Chain","short_title":"BSC","base_url":"https://bscscan.com/token/"},{"id":"99","title":"POA","short_title":"POA","base_url":"https://blockscout.com/poa/core/token/"}]' ],
|
||||
[ 'NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES', '[{"type":"omni","title":"OmniBridge","short_title":"OMNI"},{"type":"amb","title":"Arbitrary Message Bridge","short_title":"AMB"}]' ],
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ScrollL2MessageItem, ScrollL2TxnBatch } from 'types/api/scrollL2';
|
||||
|
||||
import { TX_HASH } from './tx';
|
||||
|
||||
export const SCROLL_L2_TXN_BATCH: ScrollL2TxnBatch = {
|
||||
commitment_transaction: {
|
||||
block_number: 4053979,
|
||||
hash: '0xd04d626495ef69abd37ae3ea585ed03319a3d3b50cf10874f7f36741c7b45a18',
|
||||
timestamp: '2023-08-09T08:09:12.000000Z',
|
||||
},
|
||||
confirmation_transaction: {
|
||||
block_number: null,
|
||||
hash: null,
|
||||
timestamp: null,
|
||||
},
|
||||
end_block: 1711,
|
||||
number: 273,
|
||||
start_block: 1697,
|
||||
transaction_count: 15,
|
||||
data_availability: {
|
||||
batch_data_container: 'in_blob4844',
|
||||
},
|
||||
};
|
||||
|
||||
export const SCROLL_L2_MESSAGE_ITEM: ScrollL2MessageItem = {
|
||||
id: 930795,
|
||||
origination_transaction_block_number: 20639178,
|
||||
origination_transaction_hash: TX_HASH,
|
||||
origination_timestamp: '2024-08-30T05:03:23.000000Z',
|
||||
completion_transaction_hash: 'TX_HASH',
|
||||
value: '5084131319054877748',
|
||||
};
|
||||
@@ -18,6 +18,7 @@ const PRESETS = {
|
||||
optimism_sepolia: 'https://optimism-sepolia.blockscout.com',
|
||||
polygon: 'https://polygon.blockscout.com',
|
||||
rootstock_testnet: 'https://rootstock-testnet.blockscout.com',
|
||||
scroll_sepolia: 'https://scroll-sepolia.blockscout.com',
|
||||
shibarium: 'https://www.shibariumscan.io',
|
||||
stability_testnet: 'https://stability-testnet.blockscout.com',
|
||||
zkevm: 'https://zkevm.blockscout.com',
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { Block } from './block';
|
||||
import type { Transaction } from './transaction';
|
||||
|
||||
export interface ScrollL2BatchesResponse {
|
||||
items: Array<ScrollL2TxnBatch>;
|
||||
next_page_params: {
|
||||
items_count: number;
|
||||
number: number;
|
||||
};
|
||||
}
|
||||
|
||||
type ScrollL2TxnBatchCommitmentTransaction = {
|
||||
block_number: number;
|
||||
hash: string;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
type ScrollL2TxnBatchConfirmationTransaction = {
|
||||
block_number: number | null;
|
||||
hash: string | null;
|
||||
timestamp: string | null;
|
||||
};
|
||||
|
||||
export type ScrollL2TxnBatch = {
|
||||
number: number;
|
||||
commitment_transaction: ScrollL2TxnBatchCommitmentTransaction;
|
||||
confirmation_transaction: ScrollL2TxnBatchConfirmationTransaction;
|
||||
start_block: number;
|
||||
end_block: number;
|
||||
transaction_count: number;
|
||||
data_availability: {
|
||||
batch_data_container: 'in_blob4844' | 'in_calldata';
|
||||
};
|
||||
};
|
||||
|
||||
export type ScrollL2TxnBatchTxs = {
|
||||
items: Array<Transaction>;
|
||||
next_page_params: {
|
||||
batch_number: number;
|
||||
block_number: number;
|
||||
index: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ScrollL2TxnBatchBlocks = {
|
||||
items: Array<Block>;
|
||||
next_page_params: {
|
||||
batch_number: number;
|
||||
block_number: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ScrollL2MessagesResponse = {
|
||||
items: Array<ScrollL2MessageItem>;
|
||||
next_page_params: {
|
||||
id: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ScrollL2MessageItem = {
|
||||
id: number;
|
||||
origination_transaction_block_number: number;
|
||||
origination_transaction_hash: string;
|
||||
origination_timestamp: string;
|
||||
completion_transaction_hash: string | null;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const SCROLL_L2_BLOCK_STATUSES = [
|
||||
'Confirmed by Sequencer' as const,
|
||||
'Committed' as const,
|
||||
'Finalized' as const,
|
||||
];
|
||||
|
||||
export type ScrollL2BlockStatus = typeof SCROLL_L2_BLOCK_STATUSES[number];
|
||||
@@ -5,6 +5,7 @@ import type { DecodedInput } from './decodedInput';
|
||||
import type { Fee } from './fee';
|
||||
import type { NovesTxTranslation } from './noves';
|
||||
import type { OptimisticL2WithdrawalStatus } from './optimisticL2';
|
||||
import type { ScrollL2BlockStatus } from './scrollL2';
|
||||
import type { TokenInfo } from './token';
|
||||
import type { TokenTransfer } from './tokenTransfer';
|
||||
import type { TxAction } from './txAction';
|
||||
@@ -103,6 +104,7 @@ export type Transaction = {
|
||||
// Noves-fi
|
||||
translation?: NovesTxTranslation;
|
||||
arbitrum?: ArbitrumTransactionData;
|
||||
scroll?: ScrollTransactionData;
|
||||
};
|
||||
|
||||
type ArbitrumTransactionData = {
|
||||
@@ -190,3 +192,17 @@ export interface TransactionsSorting {
|
||||
export type TransactionsSortingField = TransactionsSorting['sort'];
|
||||
|
||||
export type TransactionsSortingValue = `${ TransactionsSortingField }-${ TransactionsSorting['order'] }`;
|
||||
|
||||
export type ScrollTransactionData = {
|
||||
l1_fee: string;
|
||||
l2_fee: Fee;
|
||||
l1_fee_commit_scalar: number;
|
||||
l1_base_fee: number;
|
||||
l1_blob_base_fee: number;
|
||||
l1_fee_scalar: number;
|
||||
l1_fee_overhead: number;
|
||||
l1_fee_blob_scalar: number;
|
||||
l1_gas_used: number;
|
||||
l2_block_status: ScrollL2BlockStatus;
|
||||
queue_index: number;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ export const ROLLUP_TYPES = [
|
||||
'shibarium',
|
||||
'zkEvm',
|
||||
'zkSync',
|
||||
'scroll',
|
||||
] as const;
|
||||
|
||||
export type RollupType = ArrayElement<typeof ROLLUP_TYPES>;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Skeleton, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2MessageItem } from 'types/api/scrollL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
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 TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ScrollL2MessageItem; isLoading?: boolean };
|
||||
|
||||
const ScrollL2DepositsListItem = ({ item, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { valueStr } = getCurrencyValue({ value: item.value, decimals: String(config.chain.currency.decimals) });
|
||||
|
||||
return (
|
||||
<ListItemMobileGrid.Container>
|
||||
|
||||
<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>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Index</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ item.id }
|
||||
</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TxEntityL1
|
||||
isLoading={ isLoading }
|
||||
hash={ item.origination_transaction_hash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TimeAgoWithTooltip
|
||||
timestamp={ item.origination_timestamp }
|
||||
isLoading={ isLoading }
|
||||
display="inline-block"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 txn hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
{ item.completion_transaction_hash ? (
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
hash={ item.completion_transaction_hash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : (
|
||||
<chakra.span>
|
||||
Pending Claim
|
||||
</chakra.span>
|
||||
) }
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Value</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ `${ valueStr } ${ config.chain.currency.symbol }` }
|
||||
</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
</ListItemMobileGrid.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2DepositsListItem;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2MessageItem } from 'types/api/scrollL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
|
||||
import ScrollL2DepositsTableItem from './ScrollL2DepositsTableItem';
|
||||
|
||||
type Props = {
|
||||
items: Array<ScrollL2MessageItem>;
|
||||
top: number;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const ScrollL2DepositsTable = ({ items, top, isLoading }: Props) => {
|
||||
return (
|
||||
<Table style={{ tableLayout: 'auto' }} minW="950px">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th>L1 block</Th>
|
||||
<Th>Index</Th>
|
||||
<Th>L1 txn hash</Th>
|
||||
<Th>Age</Th>
|
||||
<Th>L2 txn hash</Th>
|
||||
<Th isNumeric>Value { config.chain.currency.symbol }</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ items.map((item, index) => (
|
||||
<ScrollL2DepositsTableItem key={ String(item.id) + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2DepositsTable;
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Td, Tr, Skeleton, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2MessageItem } from 'types/api/scrollL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
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 TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ScrollL2MessageItem; isLoading?: boolean };
|
||||
|
||||
const ScrollL2DepositsTableItem = ({ item, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { valueStr } = getCurrencyValue({ value: item.value, decimals: String(config.chain.currency.decimals) });
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td verticalAlign="middle">
|
||||
<BlockEntityL1
|
||||
number={ item.origination_transaction_block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
noIcon
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<span>{ item.id }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<TxEntityL1
|
||||
isLoading={ isLoading }
|
||||
hash={ item.origination_transaction_hash }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle" pr={ 12 }>
|
||||
<TimeAgoWithTooltip
|
||||
timestamp={ item.origination_timestamp }
|
||||
isLoading={ isLoading }
|
||||
color="text_secondary"
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ item.completion_transaction_hash ? (
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
hash={ item.completion_transaction_hash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
/>
|
||||
) : (
|
||||
<chakra.span color="text_secondary">
|
||||
Pending Claim
|
||||
</chakra.span>
|
||||
) }
|
||||
</Td>
|
||||
<Td verticalAlign="middle" isNumeric>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
<span>{ valueStr }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2DepositsTableItem;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as messagesMock from 'mocks/scroll/messages';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect, devices } from 'playwright/lib';
|
||||
|
||||
import ScrollL2Deposits from './ScrollL2Deposits';
|
||||
|
||||
test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
|
||||
await mockTextAd();
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockApiResponse('scroll_l2_deposits', messagesMock.baseResponse);
|
||||
await mockApiResponse('scroll_l2_deposits_count', 3971111);
|
||||
|
||||
const component = await render(<ScrollL2Deposits/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
|
||||
test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
|
||||
await mockTextAd();
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockApiResponse('scroll_l2_deposits', messagesMock.baseResponse);
|
||||
await mockApiResponse('scroll_l2_deposits_count', 3971111);
|
||||
|
||||
const component = await render(<ScrollL2Deposits/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
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 { SCROLL_L2_MESSAGE_ITEM } from 'stubs/scrollL2';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import ScrollL2DepositsListItem from 'ui/deposits/scrollL2/ScrollL2DepositsListItem';
|
||||
import ScrollL2DepositsTable from 'ui/deposits/scrollL2/ScrollL2DepositsTable';
|
||||
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';
|
||||
|
||||
const ScrollL2Deposits = () => {
|
||||
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
|
||||
resourceName: 'scroll_l2_deposits',
|
||||
options: {
|
||||
placeholderData: generateListStub<'scroll_l2_deposits'>(
|
||||
SCROLL_L2_MESSAGE_ITEM,
|
||||
50,
|
||||
{ next_page_params: { items_count: 50, id: 1 } },
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useApiQuery('scroll_l2_deposits_count', {
|
||||
queryOptions: {
|
||||
placeholderData: 1927029,
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map(((item, index) => (
|
||||
<ScrollL2DepositsListItem
|
||||
key={ String(item.id) + (isPlaceholderData ? index : '') }
|
||||
isLoading={ isPlaceholderData }
|
||||
item={ item }
|
||||
/>
|
||||
))) }
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<ScrollL2DepositsTable items={ data.items } 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() } deposits found
|
||||
</Skeleton>
|
||||
);
|
||||
})();
|
||||
|
||||
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={ `Deposits (L1${ nbsp }${ rightLineArrow }${ nbsp }L2)` } withTextAd/>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
items={ data?.items }
|
||||
emptyText="There are no deposits."
|
||||
content={ content }
|
||||
actionBar={ actionBar }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2Deposits;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { batchData } from 'mocks/scroll/txnBatches';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect, devices } from 'playwright/lib';
|
||||
|
||||
import ScrollL2TxnBatch from './ScrollL2TxnBatch';
|
||||
|
||||
const batchNumber = '5';
|
||||
const hooksConfig = {
|
||||
router: {
|
||||
query: { number: batchNumber },
|
||||
},
|
||||
};
|
||||
|
||||
test.beforeEach(async({ mockTextAd, mockEnvs }) => {
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockTextAd();
|
||||
});
|
||||
|
||||
test('base view', async({ render, mockApiResponse }) => {
|
||||
await mockApiResponse('scroll_l2_txn_batch', batchData, { pathParams: { number: batchNumber } });
|
||||
const component = await render(<ScrollL2TxnBatch/>, { hooksConfig });
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
|
||||
test('base view', async({ render, mockApiResponse }) => {
|
||||
await mockApiResponse('scroll_l2_txn_batch', batchData, { pathParams: { number: batchNumber } });
|
||||
const component = await render(<ScrollL2TxnBatch/>, { 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 { BLOCK } from 'stubs/block';
|
||||
import { SCROLL_L2_TXN_BATCH } from 'stubs/scrollL2';
|
||||
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 ScrollL2TxnBatchDetails from 'ui/txnBatches/scrollL2/ScrollL2TxnBatchDetails';
|
||||
import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
|
||||
|
||||
const TAB_LIST_PROPS = {
|
||||
marginBottom: 0,
|
||||
py: 5,
|
||||
marginTop: -5,
|
||||
};
|
||||
|
||||
const TABS_HEIGHT = 80;
|
||||
|
||||
const ScrollL2TxnBatch = () => {
|
||||
const router = useRouter();
|
||||
const appProps = useAppContext();
|
||||
const number = getQueryParamString(router.query.number);
|
||||
const tab = getQueryParamString(router.query.tab);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const batchQuery = useApiQuery('scroll_l2_txn_batch', {
|
||||
pathParams: { number },
|
||||
queryOptions: {
|
||||
enabled: Boolean(number),
|
||||
placeholderData: SCROLL_L2_TXN_BATCH,
|
||||
},
|
||||
});
|
||||
|
||||
const batchTxsQuery = useQueryWithPages({
|
||||
resourceName: 'scroll_l2_txn_batch_txs',
|
||||
pathParams: { number },
|
||||
options: {
|
||||
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'),
|
||||
placeholderData: generateListStub<'scroll_l2_txn_batch_txs'>(TX, 50, { next_page_params: {
|
||||
batch_number: 8122,
|
||||
block_number: 1338932,
|
||||
index: 0,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
});
|
||||
|
||||
const batchBlocksQuery = useQueryWithPages({
|
||||
resourceName: 'scroll_l2_txn_batch_blocks',
|
||||
pathParams: { number },
|
||||
options: {
|
||||
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'),
|
||||
placeholderData: generateListStub<'scroll_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: <ScrollL2TxnBatchDetails 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 txn batches list',
|
||||
url: appProps.referrer,
|
||||
};
|
||||
}, [ appProps.referrer ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
<PageTitle
|
||||
title={ `Txn 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 ScrollL2TxnBatch;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as scrollTxnBatchesMock from 'mocks/scroll/txnBatches';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect, devices } from 'playwright/lib';
|
||||
|
||||
import ScrollL2TxnBatches from './ScrollL2TxnBatches';
|
||||
|
||||
test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
|
||||
test.slow();
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockTextAd();
|
||||
await mockApiResponse('scroll_l2_txn_batches', scrollTxnBatchesMock.baseResponse);
|
||||
await mockApiResponse('scroll_l2_txn_batches_count', 9927);
|
||||
|
||||
const component = await render(<ScrollL2TxnBatches/>);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
|
||||
test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
|
||||
test.slow();
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockTextAd();
|
||||
await mockApiResponse('scroll_l2_txn_batches', scrollTxnBatchesMock.baseResponse);
|
||||
await mockApiResponse('scroll_l2_txn_batches_count', 9927);
|
||||
|
||||
const component = await render(<ScrollL2TxnBatches/>);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Hide, Show, Skeleton, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { SCROLL_L2_TXN_BATCH } from 'stubs/scrollL2';
|
||||
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 ScrollL2TxnBatchesListItem from 'ui/txnBatches/scrollL2/ScrollL2TxnBatchesListItem';
|
||||
import ScrollL2TxnBatchesTable from 'ui/txnBatches/scrollL2/ScrollL2TxnBatchesTable';
|
||||
|
||||
const ScrollL2TxnBatches = () => {
|
||||
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
|
||||
resourceName: 'scroll_l2_txn_batches',
|
||||
options: {
|
||||
placeholderData: generateListStub<'scroll_l2_txn_batches'>(
|
||||
SCROLL_L2_TXN_BATCH,
|
||||
50,
|
||||
{
|
||||
next_page_params: {
|
||||
items_count: 50,
|
||||
number: 224,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useApiQuery('scroll_l2_txn_batches_count', {
|
||||
queryOptions: {
|
||||
placeholderData: 123456,
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map(((item, index) => (
|
||||
<ScrollL2TxnBatchesListItem
|
||||
key={ item.number + (isPlaceholderData ? String(index) : '') }
|
||||
item={ item }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
))) }
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<ScrollL2TxnBatchesTable 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">
|
||||
Txn batch
|
||||
<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="Txn batches" withTextAd/>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
items={ data?.items }
|
||||
emptyText="There are no txn batches."
|
||||
content={ content }
|
||||
actionBar={ actionBar }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2TxnBatches;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as messagesMock from 'mocks/scroll/messages';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect, devices } from 'playwright/lib';
|
||||
|
||||
import ScrollL2Withdrawals from './ScrollL2Withdrawals';
|
||||
|
||||
test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
|
||||
await mockTextAd();
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockApiResponse('scroll_l2_withdrawals', messagesMock.baseResponse);
|
||||
await mockApiResponse('scroll_l2_withdrawals_count', 3971111);
|
||||
|
||||
const component = await render(<ScrollL2Withdrawals/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
|
||||
test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
|
||||
await mockTextAd();
|
||||
await mockEnvs(ENVS_MAP.scrollRollup);
|
||||
await mockApiResponse('scroll_l2_withdrawals', messagesMock.baseResponse);
|
||||
await mockApiResponse('scroll_l2_withdrawals_count', 3971111);
|
||||
|
||||
const component = await render(<ScrollL2Withdrawals/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
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 { SCROLL_L2_MESSAGE_ITEM } from 'stubs/scrollL2';
|
||||
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 ScrollL2WithdrawalsListItem from 'ui/withdrawals/scrollL2/ScrollL2WithdrawalsListItem';
|
||||
import ScrollL2WithdrawalsTable from 'ui/withdrawals/scrollL2/ScrollL2WithdrawalsTable';
|
||||
|
||||
const ScrollL2Withdrawals = () => {
|
||||
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
|
||||
resourceName: 'scroll_l2_withdrawals',
|
||||
options: {
|
||||
placeholderData: generateListStub<'scroll_l2_withdrawals'>(
|
||||
SCROLL_L2_MESSAGE_ITEM,
|
||||
50,
|
||||
{ next_page_params: { items_count: 50, id: 1 } },
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useApiQuery('scroll_l2_withdrawals_count', {
|
||||
queryOptions: {
|
||||
placeholderData: 1927029,
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map(((item, index) => (
|
||||
<ScrollL2WithdrawalsListItem
|
||||
key={ String(item.id) + (isPlaceholderData ? index : '') }
|
||||
isLoading={ isPlaceholderData }
|
||||
item={ item }
|
||||
/>
|
||||
))) }
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<ScrollL2WithdrawalsTable items={ data.items } 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() } withdrawals found
|
||||
</Skeleton>
|
||||
);
|
||||
})();
|
||||
|
||||
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={ `Withdrawals (L2${ nbsp }${ rightLineArrow }${ nbsp }L1)` } withTextAd/>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
items={ data?.items }
|
||||
emptyText="There are no withdrawals."
|
||||
content={ content }
|
||||
actionBar={ actionBar }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2Withdrawals;
|
||||
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2TxnBatch } from 'types/api/scrollL2';
|
||||
import type { ExcludeUndefined } from 'types/utils';
|
||||
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
|
||||
export interface Props {
|
||||
container: ExcludeUndefined<ScrollL2TxnBatch['data_availability']['batch_data_container']>;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ScrollL2TxnBatchDA = ({ container, isLoading }: Props) => {
|
||||
|
||||
const text = (() => {
|
||||
switch (container) {
|
||||
case 'in_blob4844':
|
||||
return 'EIP-4844 blob';
|
||||
case 'in_calldata':
|
||||
return 'Calldata';
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<Tag colorScheme="yellow" isLoading={ isLoading }>
|
||||
{ text }
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ScrollL2TxnBatchDA);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { StatusTagType } from './StatusTag';
|
||||
import StatusTag from './StatusTag';
|
||||
|
||||
export interface Props {
|
||||
status: 'Finalized' | 'Committed';
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ZkEvmL2TxnBatchStatus = ({ 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 ZkEvmL2TxnBatchStatus;
|
||||
@@ -6,9 +6,9 @@ import type { Transaction } from 'types/api/transaction';
|
||||
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
|
||||
type Props = Pick<Transaction, 'nonce' | 'type' | 'position'>;
|
||||
type Props = Pick<Transaction, 'nonce' | 'type' | 'position'> & { queueIndex?: number };
|
||||
|
||||
const TxDetailsOther = ({ nonce, type, position }: Props) => {
|
||||
const TxDetailsOther = ({ nonce, type, position, queueIndex }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
@@ -27,10 +27,17 @@ const TxDetailsOther = ({ nonce, type, position }: Props) => {
|
||||
{ type === 3 && <Text fontWeight="400" as="span" ml={ 1 } variant="secondary">(EIP-4844)</Text> }
|
||||
</Box>
|
||||
),
|
||||
<Box key="nonce">
|
||||
<Text as="span" fontWeight="500">Nonce: </Text>
|
||||
<Text fontWeight="600" as="span">{ nonce }</Text>
|
||||
</Box>,
|
||||
queueIndex !== undefined ? (
|
||||
<Box key="queueIndex">
|
||||
<Text as="span" fontWeight="500">Queue index: </Text>
|
||||
<Text fontWeight="600" as="span">{ queueIndex }</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<Box key="nonce">
|
||||
<Text as="span" fontWeight="500">Nonce: </Text>
|
||||
<Text fontWeight="600" as="span">{ nonce }</Text>
|
||||
</Box>
|
||||
),
|
||||
position !== null && position !== undefined && (
|
||||
<Box key="position">
|
||||
<Text as="span" fontWeight="500">Position: </Text>
|
||||
|
||||
@@ -16,6 +16,7 @@ import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
import { scroller, Element } from 'react-scroll';
|
||||
|
||||
import { SCROLL_L2_BLOCK_STATUSES } from 'types/api/scrollL2';
|
||||
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';
|
||||
@@ -63,6 +64,8 @@ import TxAllowedPeekers from 'ui/tx/TxAllowedPeekers';
|
||||
import TxSocketAlert from 'ui/tx/TxSocketAlert';
|
||||
import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo';
|
||||
|
||||
import TxInfoScrollFees from './TxInfoScrollFees';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
interface Props {
|
||||
@@ -166,7 +169,8 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
{
|
||||
rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum') ?
|
||||
rollupFeature.isEnabled &&
|
||||
(rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum' || rollupFeature.type === 'scroll') ?
|
||||
'L2 status and method' :
|
||||
'Status and method'
|
||||
}
|
||||
@@ -297,6 +301,12 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
</Skeleton>
|
||||
</>
|
||||
) }
|
||||
{ data.scroll?.l2_block_status && (
|
||||
<>
|
||||
<TextSeparator color="gray.500"/>
|
||||
<VerificationSteps steps={ SCROLL_L2_BLOCK_STATUSES } currentStep={ data.scroll.l2_block_status } isLoading={ isLoading }/>
|
||||
</>
|
||||
) }
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
{ data.zkevm_batch_number && !config.UI.views.tx.hiddenFields?.batch && (
|
||||
@@ -661,6 +671,20 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.scroll?.l1_gas_used !== undefined && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Total gas used on L1"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 Gas used
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isLoading }>{ BigNumber(data.scroll?.l1_gas_used || 0).toFormat() }</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ !config.UI.views.tx.hiddenFields?.gas_fees &&
|
||||
(data.base_fee_per_gas || data.max_fee_per_gas || data.max_priority_fee_per_gas) && (
|
||||
<>
|
||||
@@ -767,6 +791,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
) }
|
||||
</>
|
||||
) }
|
||||
<TxInfoScrollFees data={ data } isLoading={ isLoading }/>
|
||||
|
||||
<GridItem colSpan={{ base: undefined, lg: 2 }}>
|
||||
<Element name="TxInfo__cutLink">
|
||||
@@ -870,7 +895,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
|
||||
</>
|
||||
) }
|
||||
|
||||
<TxDetailsOther nonce={ data.nonce } type={ data.type } position={ data.position }/>
|
||||
<TxDetailsOther nonce={ data.nonce } type={ data.type } position={ data.position } queueIndex={ data.scroll?.queue_index }/>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
hint="Binary data included with the transaction. See logs tab for additional info"
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Skeleton, Text } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
import type { Transaction } from 'types/api/transaction';
|
||||
|
||||
import { WEI_IN_GWEI } from 'lib/consts';
|
||||
import { currencyUnits } from 'lib/units';
|
||||
import CurrencyValue from 'ui/shared/CurrencyValue';
|
||||
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
|
||||
type Props = {
|
||||
data: Transaction;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
export const TxInfoScrollFees = ({ data, isLoading }: Props) => {
|
||||
return (
|
||||
<>
|
||||
{ data.scroll?.l1_fee !== undefined && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
|
||||
hint="L1 fee that pays for rollup costs."
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 data fee
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<CurrencyValue
|
||||
value={ data.scroll?.l1_fee }
|
||||
currency={ currencyUnits.ether }
|
||||
exchangeRate={ data.exchange_rate }
|
||||
flexWrap="wrap"
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.scroll?.l2_fee !== undefined && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L2 execution fee"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
Execution fee
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<CurrencyValue
|
||||
value={ data.scroll?.l2_fee.value }
|
||||
currency={ currencyUnits.ether }
|
||||
exchangeRate={ data.exchange_rate }
|
||||
flexWrap="wrap"
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.scroll?.l1_fee_commit_scalar !== undefined && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Commitment scalar"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 commit scalar
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<CurrencyValue
|
||||
value={ data.scroll?.l1_fee_commit_scalar }
|
||||
currency={ currencyUnits.ether }
|
||||
exchangeRate={ data.exchange_rate }
|
||||
flexWrap="wrap"
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ data.scroll?.l1_fee_overhead !== undefined && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="Additional gas overhead of a data commitment transaction"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 Fee Overhead
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<CurrencyValue
|
||||
value={ data.scroll?.l1_fee_overhead }
|
||||
currency={ currencyUnits.ether }
|
||||
exchangeRate={ data.exchange_rate }
|
||||
flexWrap="wrap"
|
||||
/>
|
||||
</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
{ (data.scroll?.l1_base_fee !== undefined || data.scroll?.l1_fee_scalar !== undefined) && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L1 gas fees"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 gas fees
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.scroll?.l1_base_fee !== undefined && (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<Text as="span" fontWeight="500">Base: </Text>
|
||||
<Text fontWeight="600" as="span">{ BigNumber(data.scroll?.l1_base_fee || 0).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
|
||||
</Skeleton>
|
||||
) }
|
||||
{ data.scroll?.l1_fee_scalar !== undefined && (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<TextSeparator/>
|
||||
<Text as="span" fontWeight="500">Scalar: </Text>
|
||||
<Text fontWeight="600" as="span">{ BigNumber(data.scroll?.l1_fee_scalar || 0).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
|
||||
</Skeleton>
|
||||
) }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
{ (data.scroll?.l1_blob_base_fee !== undefined || data.scroll?.l1_fee_blob_scalar !== undefined) && (
|
||||
<>
|
||||
<DetailsInfoItem.Label
|
||||
hint="L1 blob fees"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
L1 blob fees
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.scroll?.l1_blob_base_fee !== undefined && (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<Text as="span" fontWeight="500">Base: </Text>
|
||||
<Text fontWeight="600" as="span">{ BigNumber(data.scroll?.l1_blob_base_fee || 0).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
|
||||
</Skeleton>
|
||||
) }
|
||||
{ data.scroll?.l1_fee_blob_scalar !== undefined && (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<TextSeparator/>
|
||||
<Text as="span" fontWeight="500">Scalar: </Text>
|
||||
<Text fontWeight="600" as="span">{ BigNumber(data.scroll?.l1_fee_blob_scalar || 0).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
|
||||
</Skeleton>
|
||||
) }
|
||||
</DetailsInfoItem.Value>
|
||||
</>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TxInfoScrollFees;
|
||||
@@ -0,0 +1,219 @@
|
||||
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 { ScrollL2TxnBatch } from 'types/api/scrollL2';
|
||||
|
||||
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 ScrollL2TxnBatchDA from 'ui/shared/batch/ScrollL2TxnBatchDA';
|
||||
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 LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import PrevNext from 'ui/shared/PrevNext';
|
||||
import ScrollL2TxnBatchStatus from 'ui/shared/statusTag/ScrollL2TxnBatchStatus';
|
||||
|
||||
interface Props {
|
||||
query: UseQueryResult<ScrollL2TxnBatch, ResourceError>;
|
||||
}
|
||||
|
||||
const ScrollL2TxnBatchDetails = ({ 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"
|
||||
>
|
||||
Txn batch number
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>
|
||||
{ data.number }
|
||||
</Skeleton>
|
||||
<PrevNext
|
||||
ml={ 6 }
|
||||
onClick={ handlePrevNextClick }
|
||||
prevLabel="View previous txn batch"
|
||||
nextLabel="View next txn batch"
|
||||
isPrevDisabled={ data.number === 0 }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Data availability container"
|
||||
>
|
||||
Container
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<ScrollL2TxnBatchDA container={ data.data_availability.batch_data_container } isLoading={ isPlaceholderData }/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Status of this batch"
|
||||
>
|
||||
Status
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<ScrollL2TxnBatchStatus status={ data.confirmation_transaction.hash ? 'Finalized' : 'Committed' } isLoading={ isPlaceholderData }/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Date and time at which batch is finalized to L1"
|
||||
>
|
||||
Finalized timestamp
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.confirmation_transaction.timestamp ?
|
||||
<DetailsTimestamp timestamp={ data.confirmation_transaction.timestamp }isLoading={ isPlaceholderData }/> :
|
||||
<Skeleton isLoaded={ !isPlaceholderData } display="inline-block">Pending</Skeleton>
|
||||
}
|
||||
</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.transaction_count.toLocaleString() } transaction{ data.transaction_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.toLocaleString() } block{ blocksCount === 1 ? '' : 's' }
|
||||
</LinkInternal>
|
||||
</Skeleton>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Date and time at which batch is committed to L1"
|
||||
>
|
||||
Committed timestamp
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.commitment_transaction.timestamp ?
|
||||
<DetailsTimestamp timestamp={ data.commitment_transaction.timestamp }isLoading={ isPlaceholderData }/> :
|
||||
<Skeleton isLoaded={ !isPlaceholderData } display="inline-block">Pending</Skeleton>
|
||||
}
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Hash of L1 transaction this batch was committed in"
|
||||
>
|
||||
Committed transaction hash
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<TxEntityL1
|
||||
isLoading={ isPlaceholderData }
|
||||
hash={ data.commitment_transaction.hash }
|
||||
maxW="100%"
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="L1 block that includes transaction with this batch commitment"
|
||||
>
|
||||
Committed block
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
<BlockEntityL1
|
||||
isLoading={ isPlaceholderData }
|
||||
number={ data.commitment_transaction.block_number }
|
||||
/>
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="Hash of L1 transaction this batch was finalized in"
|
||||
>
|
||||
Finalized transaction hash
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.confirmation_transaction.hash ? (
|
||||
<TxEntityL1
|
||||
isLoading={ isPlaceholderData }
|
||||
hash={ data.confirmation_transaction.hash }
|
||||
maxW="100%"
|
||||
/>
|
||||
) : <Skeleton isLoaded={ !isPlaceholderData } display="inline-block">Pending</Skeleton> }
|
||||
</DetailsInfoItem.Value>
|
||||
|
||||
<DetailsInfoItem.Label
|
||||
isLoading={ isPlaceholderData }
|
||||
hint="L1 block that includes transaction with this batch finalization data"
|
||||
>
|
||||
Finalized block
|
||||
</DetailsInfoItem.Label>
|
||||
<DetailsInfoItem.Value>
|
||||
{ data.confirmation_transaction.block_number ? (
|
||||
<BlockEntityL1
|
||||
isLoading={ isPlaceholderData }
|
||||
number={ data.confirmation_transaction.block_number }
|
||||
/>
|
||||
) : <Skeleton isLoaded={ !isPlaceholderData } display="inline-block">Pending</Skeleton> }
|
||||
</DetailsInfoItem.Value>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2TxnBatchDetails;
|
||||
@@ -0,0 +1,135 @@
|
||||
import { Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2TxnBatch } from 'types/api/scrollL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import ScrollL2TxnBatchDA from 'ui/shared/batch/ScrollL2TxnBatchDA';
|
||||
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 ScrollL2TxnBatchStatus from 'ui/shared/statusTag/ScrollL2TxnBatchStatus';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ScrollL2TxnBatch; isLoading?: boolean };
|
||||
|
||||
const ScrollL2TxnBatchesListItem = ({ item, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') {
|
||||
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 }>Data availability</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<ScrollL2TxnBatchDA container={ item.data_availability.batch_data_container } isLoading={ isLoading }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<ScrollL2TxnBatchStatus status={ item.confirmation_transaction.hash ? 'Finalized' : 'Committed' } isLoading={ isLoading }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Committed block</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BlockEntityL1
|
||||
number={ item.commitment_transaction.block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Committed txn hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TxEntityL1
|
||||
hash={ item.commitment_transaction.hash }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TimeAgoWithTooltip
|
||||
timestamp={ item.commitment_transaction.timestamp }
|
||||
fallbackText="Undefined"
|
||||
isLoading={ isLoading }
|
||||
display="inline-block"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Finalized block</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
{ item.confirmation_transaction.block_number ? (
|
||||
<BlockEntityL1
|
||||
number={ item.confirmation_transaction.block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
) : <Skeleton isLoaded={ !isLoading } display="inline-block">Pending</Skeleton> }
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Finalized txn hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
{ item.confirmation_transaction.hash ? (
|
||||
<TxEntityL1
|
||||
hash={ item.confirmation_transaction.hash }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
) : <Skeleton isLoaded={ !isLoading } display="inline-block">Pending</Skeleton> }
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Blocks count</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'blocks' } }) }
|
||||
isLoading={ isLoading }
|
||||
fontWeight={ 600 }
|
||||
>
|
||||
<Skeleton isLoaded={ !isLoading } minW="40px">
|
||||
{ (item.end_block - item.start_block + 1).toLocaleString() }
|
||||
</Skeleton>
|
||||
</LinkInternal>
|
||||
</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.transaction_count.toLocaleString() }
|
||||
</Skeleton>
|
||||
</LinkInternal>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
</ListItemMobileGrid.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2TxnBatchesListItem;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2TxnBatch } from 'types/api/scrollL2';
|
||||
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
|
||||
import ScrollL2TxnBatchesTableItem from './ScrollL2TxnBatchesTableItem';
|
||||
|
||||
type Props = {
|
||||
items: Array<ScrollL2TxnBatch>;
|
||||
top: number;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const ScrollL2TxnBatchesTable = ({ items, top, isLoading }: Props) => {
|
||||
return (
|
||||
<Table minW="1000px" style={{ tableLayout: 'auto' }}>
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th>Batch #</Th>
|
||||
<Th>Container</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Committed block</Th>
|
||||
<Th>Committed txn hash</Th>
|
||||
<Th>Age</Th>
|
||||
<Th>Finalized block</Th>
|
||||
<Th>Finalized txn hash</Th>
|
||||
<Th isNumeric>Blocks</Th>
|
||||
<Th isNumeric>Txn</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ items.map((item, index) => (
|
||||
<ScrollL2TxnBatchesTableItem
|
||||
key={ item.number + (isLoading ? String(index) : '') }
|
||||
item={ item }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2TxnBatchesTable;
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Td, Tr, Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2TxnBatch } from 'types/api/scrollL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import ScrollL2TxnBatchDA from 'ui/shared/batch/ScrollL2TxnBatchDA';
|
||||
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 ScrollL2TxnBatchStatus from 'ui/shared/statusTag/ScrollL2TxnBatchStatus';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ScrollL2TxnBatch; isLoading?: boolean };
|
||||
|
||||
const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td verticalAlign="middle">
|
||||
<BatchEntityL2
|
||||
isLoading={ isLoading }
|
||||
number={ item.number }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
noIcon
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<ScrollL2TxnBatchDA container={ item.data_availability?.batch_data_container } isLoading={ isLoading }/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<ScrollL2TxnBatchStatus status={ item.confirmation_transaction.hash ? 'Finalized' : 'Committed' } isLoading={ isLoading }/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<BlockEntityL1
|
||||
number={ item.commitment_transaction.block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<TxEntityL1
|
||||
hash={ item.commitment_transaction.hash }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<TimeAgoWithTooltip
|
||||
timestamp={ item.commitment_transaction.timestamp }
|
||||
fallbackText="Undefined"
|
||||
isLoading={ isLoading }
|
||||
color="text_secondary"
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ item.confirmation_transaction.block_number ? (
|
||||
<BlockEntityL1
|
||||
number={ item.confirmation_transaction.block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
) : <Skeleton isLoaded={ !isLoading } display="inline-block">Pending</Skeleton> }
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ item.confirmation_transaction.hash ? (
|
||||
<TxEntityL1
|
||||
hash={ item.confirmation_transaction.hash }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : <Skeleton isLoaded={ !isLoading } display="inline-block">Pending</Skeleton> }
|
||||
</Td>
|
||||
<Td verticalAlign="middle" isNumeric>
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'blocks' } }) }
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
{ (item.end_block - item.start_block + 1).toLocaleString() }
|
||||
</Skeleton>
|
||||
</LinkInternal>
|
||||
</Td>
|
||||
<Td verticalAlign="middle" isNumeric>
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) }
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
{ item.transaction_count.toLocaleString() }
|
||||
</Skeleton>
|
||||
</LinkInternal>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default TxnBatchesTableItem;
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Skeleton, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2MessageItem } from 'types/api/scrollL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ScrollL2MessageItem; isLoading?: boolean };
|
||||
|
||||
const ScrollL2WithdrawalsListItem = ({ item, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { valueStr } = getCurrencyValue({ value: item.value, decimals: String(config.chain.currency.decimals) });
|
||||
|
||||
return (
|
||||
<ListItemMobileGrid.Container>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 block</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BlockEntity
|
||||
number={ item.origination_transaction_block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Index</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ item.id }
|
||||
</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 txn hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
hash={ item.origination_transaction_hash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TimeAgoWithTooltip
|
||||
timestamp={ item.origination_timestamp }
|
||||
isLoading={ isLoading }
|
||||
display="inline-block"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
{ item.completion_transaction_hash ? (
|
||||
<TxEntityL1
|
||||
isLoading={ isLoading }
|
||||
hash={ item.completion_transaction_hash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
/>
|
||||
) : (
|
||||
<chakra.span>
|
||||
Pending Claim
|
||||
</chakra.span>
|
||||
) }
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Value</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ `${ valueStr } ${ config.chain.currency.symbol }` }
|
||||
</Skeleton>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
</ListItemMobileGrid.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2WithdrawalsListItem;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2MessageItem } from 'types/api/scrollL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
|
||||
import ScrollL2WithdrawalsTableItem from './ScrollL2WithdrawalsTableItem';
|
||||
|
||||
type Props = {
|
||||
items: Array<ScrollL2MessageItem>;
|
||||
top: number;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const ScrollL2WithdrawalsTable = ({ items, top, isLoading }: Props) => {
|
||||
return (
|
||||
<Table style={{ tableLayout: 'auto' }} minW="950px">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th>L2 block</Th>
|
||||
<Th>Index</Th>
|
||||
<Th>L2 txn hash</Th>
|
||||
<Th>Age</Th>
|
||||
<Th>L1 txn hash</Th>
|
||||
<Th isNumeric>{ `Value ${ config.chain.currency.symbol }` }</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ items.map((item, index) => (
|
||||
<ScrollL2WithdrawalsTableItem key={ String(item.id) + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2WithdrawalsTable;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Td, Tr, Skeleton, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ScrollL2MessageItem } from 'types/api/scrollL2';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
type Props = { item: ScrollL2MessageItem; isLoading?: boolean };
|
||||
|
||||
const ScrollL2WithdrawalsTableItem = ({ item, isLoading }: Props) => {
|
||||
if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { valueStr } = getCurrencyValue({ value: item.value, decimals: String(config.chain.currency.decimals) });
|
||||
return (
|
||||
<Tr>
|
||||
<Td verticalAlign="middle">
|
||||
<BlockEntity
|
||||
number={ item.origination_transaction_block_number }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 600 }
|
||||
noIcon
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<span>{ item.id }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
hash={ item.origination_transaction_hash }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle" pr={ 12 }>
|
||||
<TimeAgoWithTooltip
|
||||
timestamp={ item.origination_timestamp }
|
||||
isLoading={ isLoading }
|
||||
color="text_secondary"
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ item.completion_transaction_hash ? (
|
||||
<TxEntityL1
|
||||
isLoading={ isLoading }
|
||||
hash={ item.completion_transaction_hash }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
) : (
|
||||
<chakra.span color="text_secondary">
|
||||
Pending Claim
|
||||
</chakra.span>
|
||||
) }
|
||||
</Td>
|
||||
<Td verticalAlign="middle" isNumeric>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
<span>{ valueStr }</span>
|
||||
</Skeleton>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollL2WithdrawalsTableItem;
|
||||