diff --git a/.github/workflows/deploy-review-l2.yml b/.github/workflows/deploy-review-l2.yml index 721274589a..fa46d4c558 100644 --- a/.github/workflows/deploy-review-l2.yml +++ b/.github/workflows/deploy-review-l2.yml @@ -34,6 +34,7 @@ on: - zkevm - zilliqa_prototestnet - zksync + - via - zora jobs: diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index cb3237a5d7..2a1bdfc466 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -37,6 +37,7 @@ on: - zkevm - zilliqa_prototestnet - zksync + - via - zora jobs: diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 16fcb132fc..889eb348e1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -386,6 +386,7 @@ "zkevm", "zilliqa_prototestnet", "zksync", + "via", "zora", ], "default": "main" diff --git a/configs/app/ui.ts b/configs/app/ui.ts index ba0d3ad362..79b36c9e4c 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -32,7 +32,7 @@ const homePageStats: Array = (() => { if (!Array.isArray(parsedValue)) { const rollupFeature = features.rollup; - if (rollupFeature.isEnabled && [ 'zkEvm', 'zkSync', 'arbitrum' ].includes(rollupFeature.type)) { + if (rollupFeature.isEnabled && [ 'zkEvm', 'zkSync', 'via', 'arbitrum' ].includes(rollupFeature.type)) { return [ 'latest_batch', 'average_block_time', 'total_txs', 'wallet_addresses', 'gas_tracker' ]; } diff --git a/configs/envs/.env.via_testnet b/configs/envs/.env.via_testnet new file mode 100644 index 0000000000..3a14404c58 --- /dev/null +++ b/configs/envs/.env.via_testnet @@ -0,0 +1,54 @@ +# Set of ENVs for VIA network explorer +# https://via.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=via" + +# 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_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none + +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=via.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zksync.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/zksync.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x79c7802ccdf3be5a49c47cc751aad351b0027e8275f6f54878eda50ee559a648 +NEXT_PUBLIC_HOMEPAGE_CHARTS=[] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(53, 103, 246, 1) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://via.us.auth0.com/v2/logout +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Bitcoin +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=BTC +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME=satoshi +NEXT_PUBLIC_NETWORK_EXPLORERS=[] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/main/configs/network-logos/via-light.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/main/configs/network-logos/via-dark.svg +NEXT_PUBLIC_NETWORK_ID=25223 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/main/configs/network-logos/via-light.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/main/configs/network-logos/via-dark.svg +NEXT_PUBLIC_NETWORK_NAME=VIA Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://mainnet.era.zksync.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=VIA Testnet +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=false +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zksync.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://zksync.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://mempool.space/testnet +NEXT_PUBLIC_ROLLUP_TYPE=via +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-zksync.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-zksync-era-mainnet.k8s-prod-2.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/docs/ENVS.md b/docs/ENVS.md index 05fa5d75cc..6cb493e281 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -132,7 +132,7 @@ All json-like values should be single-quoted. If it contains a hash (`#`) or a d | Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | --- | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'daily_operational_txs' \| 'coin_price' \| 'secondary_coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` | v1.0.x+ | -| NEXT_PUBLIC_HOMEPAGE_STATS | `Array<'latest_batch' \| 'total_blocks' \| 'average_block_time' \| 'total_txs' \| 'total_operational_txs' \| 'latest_l1_state_batch' \| 'wallet_addresses' \| 'gas_tracker' \| 'btc_locked' \| 'current_epoch'>` | List of stats widgets displayed on the home page | - | For zkSync, zkEvm and Arbitrum rollups: `['latest_batch','average_block_time','total_txs','wallet_addresses','gas_tracker']`, for other cases: `['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker']` | `['total_blocks','total_txs','wallet_addresses']` | v1.35.x+ | +| NEXT_PUBLIC_HOMEPAGE_STATS | `Array<'latest_batch' \| 'total_blocks' \| 'average_block_time' \| 'total_txs' \| 'total_operational_txs' \| 'latest_l1_state_batch' \| 'wallet_addresses' \| 'gas_tracker' \| 'btc_locked' \| 'current_epoch'>` | List of stats widgets displayed on the home page | - | For zkSync, via, zkEvm and Arbitrum rollups: `['latest_batch','average_block_time','total_txs','wallet_addresses','gas_tracker']`, for other cases: `['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker']` | `['total_blocks','total_txs','wallet_addresses']` | v1.35.x+ | | NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead). **DEPRECATED** _Use `NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG` instead_ | - | `white` | `\#DCFE76` | v1.0.x+ | | NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead). **DEPRECATED** _Use `NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG` instead_ | - | `radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` | v1.1.0+ | | NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG | `HeroBannerConfig`, see details [below](#hero-banner-configuration-properties) | Configuration of hero banner appearance. | - | - | See [below](#hero-banner-configuration-properties) | v1.35.0+ | @@ -461,7 +461,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' \| 'scroll'` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ | +| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' \| 'via' \| 'scroll'` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ | | NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network. **DEPRECATED** _Use `NEXT_PUBLIC_ROLLUP_PARENT_CHAIN` instead_ | 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+ | diff --git a/lib/api/services/general/misc.ts b/lib/api/services/general/misc.ts index 1751f9f9b1..0c6c025616 100644 --- a/lib/api/services/general/misc.ts +++ b/lib/api/services/general/misc.ts @@ -95,6 +95,9 @@ export const GENERAL_API_MISC_RESOURCES = { homepage_zksync_latest_batch: { path: '/api/v2/main-page/zksync/batches/latest-number', }, + homepage_via_latest_batch: { + path: '/api/v2/main-page/via/batches/latest-number', + }, homepage_arbitrum_latest_batch: { path: '/api/v2/main-page/arbitrum/batches/latest-number', }, @@ -255,6 +258,7 @@ R extends 'general:homepage_arbitrum_l2_batches' ? { items: Array : R extends 'general:search' ? SearchResult : diff --git a/lib/api/services/general/rollup.ts b/lib/api/services/general/rollup.ts index 480abd15a1..44e08e69bb 100644 --- a/lib/api/services/general/rollup.ts +++ b/lib/api/services/general/rollup.ts @@ -40,6 +40,7 @@ import type { ScrollL2MessagesResponse, } from 'types/api/scrollL2'; import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium'; +import type { ViaBatch, ViaBatchesResponse, ViaBatchTxs } from 'types/api/viaL2'; import type { ZkEvmL2DepositsResponse, ZkEvmL2TxnBatch, @@ -227,6 +228,26 @@ export const GENERAL_API_ROLLUP_RESOURCES = { paginated: true, }, + // via + via_l2_txn_batches: { + path: '/api/v2/via/batches', + filterFields: [], + paginated: true, + }, + via_l2_txn_batches_count: { + path: '/api/v2/via/batches/count', + }, + via_l2_txn_batch: { + path: '/api/v2/via/batches/:number', + pathParams: [ 'number' as const ], + }, + via_l2_txn_batch_txs: { + path: '/api/v2/transactions/via-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + paginated: true, + }, + // zkEvm zkevm_l2_deposits: { path: '/api/v2/zkevm/deposits', @@ -370,6 +391,10 @@ R extends 'general:zksync_l2_txn_batches' ? ZkSyncBatchesResponse : R extends 'general:zksync_l2_txn_batches_count' ? number : R extends 'general:zksync_l2_txn_batch' ? ZkSyncBatch : R extends 'general:zksync_l2_txn_batch_txs' ? ZkSyncBatchTxs : +R extends 'general:via_l2_txn_batches' ? ViaBatchesResponse : +R extends 'general:via_l2_txn_batches_count' ? number : +R extends 'general:via_l2_txn_batch' ? ViaBatch : +R extends 'general:via_l2_txn_batch_txs' ? ViaBatchTxs : R extends 'general:scroll_l2_txn_batch_txs' ? ScrollL2TxnBatchTxs : R extends 'general:scroll_l2_txn_batch_blocks' ? ScrollL2TxnBatchBlocks : R extends 'general:scroll_l2_txn_batches' ? ScrollL2BatchesResponse : diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index b8289d809e..8c196f5255 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -183,6 +183,22 @@ export default function useNavItems(): ReturnType { ensLookup, ].filter(Boolean), ]; + } else if (rollupFeature.isEnabled && rollupFeature.type === 'via') { + blockchainNavItems = [ + [ + txs, + internalTxs, + userOps, + blocks, + rollupTxnBatches, + ].filter(Boolean), + [ + topAccounts, + validators, + verifiedContracts, + ensLookup, + ].filter(Boolean), + ]; } else { blockchainNavItems = [ txs, diff --git a/mocks/contract/info.ts b/mocks/contract/info.ts index b9a06aa243..2ff76f6c7f 100644 --- a/mocks/contract/info.ts +++ b/mocks/contract/info.ts @@ -104,6 +104,13 @@ export const zkSync: SmartContract = { optimization_runs: 's', }; +export const via: SmartContract = { + ...verified, + zk_compiler_version: 'v1.2.5', + optimization_enabled: true, + optimization_runs: 's', +}; + export const stylusRust: SmartContract = { ...verified, language: 'stylus_rust', diff --git a/mocks/via/viaTxnBatch.ts b/mocks/via/viaTxnBatch.ts new file mode 100644 index 0000000000..77e6baaa17 --- /dev/null +++ b/mocks/via/viaTxnBatch.ts @@ -0,0 +1,20 @@ +import type { ViaBatch } from 'types/api/viaL2'; + +export const base: ViaBatch = { + commit_transaction_hash: '0x7cd80c88977c2b310f79196b0b2136da18012be015ce80d0d9e9fe6cfad52b16', + commit_transaction_timestamp: '2022-03-19T09:37:38.726996Z', + end_block_number: 1245490, + execute_transaction_hash: '0x110b9a19afbabd5818a996ab2b493a9b23c888d73d95f1ab5272dbae503e103a', + execute_transaction_timestamp: '2022-03-19T10:29:05.358066Z', + l1_gas_price: '4173068062', + l1_transactions_count: 0, + l2_fair_gas_price: '100000000', + l2_transactions_count: 287, + number: 8051, + prove_transaction_hash: '0xb424162ba5afe17c710dceb5fc8d15d7d46a66223454dae8c74aa39f6802625b', + prove_transaction_timestamp: '2022-03-19T10:29:05.279179Z', + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', + start_block_number: 1245209, + status: 'Executed on L1', + timestamp: '2022-03-19T09:05:49.000000Z', +}; diff --git a/mocks/via/viaTxnBatches.ts b/mocks/via/viaTxnBatches.ts new file mode 100644 index 0000000000..b9ef56ea24 --- /dev/null +++ b/mocks/via/viaTxnBatches.ts @@ -0,0 +1,49 @@ +import type { ViaBatchesItem, ViaBatchesResponse } from 'types/api/viaL2'; + +export const sealed: ViaBatchesItem = { + commit_transaction_hash: null, + commit_transaction_timestamp: null, + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8055, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sealed on L2', + timestamp: '2022-03-19T12:53:36.000000Z', + transactions_count: 738, +}; + +export const sent: ViaBatchesItem = { + commit_transaction_hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', + commit_transaction_timestamp: '2022-03-19T13:09:07.357570Z', + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8054, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sent to L1', + timestamp: '2022-03-19T11:36:45.000000Z', + transactions_count: 766, +}; + +export const executed: ViaBatchesItem = { + commit_transaction_hash: '0xa2628f477e1027ac1c60fa75c186b914647769ac1cb9c7e1cab50b13506a0035', + commit_transaction_timestamp: '2022-03-19T11:52:18.963659Z', + execute_transaction_hash: '0xb7bd6b2b17498c66d3f6e31ac3685133a81b7f728d4f6a6f42741daa257d0d68', + execute_transaction_timestamp: '2022-03-19T13:28:16.712656Z', + number: 8053, + prove_transaction_hash: '0x9d44f2b775bd771f8a53205755b3897929aa672d2cd419b3b988c16d41d4f21e', + prove_transaction_timestamp: '2022-03-19T13:28:16.603104Z', + status: 'Executed on L1', + timestamp: '2022-03-19T10:01:52.000000Z', + transactions_count: 1071, +}; + +export const baseResponse: ViaBatchesResponse = { + items: [ + sealed, + sent, + executed, + ], + next_page_params: null, +}; diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index e9ea5faab5..2e99be0f48 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -155,7 +155,7 @@ export const outputRoots: GetServerSideProps = async(context) => { return base(context); }; -const BATCH_ROLLUP_TYPES: Array = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic', 'scroll' ]; +const BATCH_ROLLUP_TYPES: Array = [ 'zkEvm', 'zkSync', 'via', 'arbitrum', 'optimistic', 'scroll' ]; export const batch: GetServerSideProps = async(context) => { if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) { return { diff --git a/pages/batches/[number].tsx b/pages/batches/[number].tsx index 6239aefafe..224d4ad7ad 100644 --- a/pages/batches/[number].tsx +++ b/pages/batches/[number].tsx @@ -23,6 +23,8 @@ const Batch = dynamic(() => { return import('ui/pages/ZkEvmL2TxnBatch'); case 'zkSync': return import('ui/pages/ZkSyncL2TxnBatch'); + case 'via': + return import('ui/pages/ViaL2TxnBatch'); case 'scroll': return import('ui/pages/ScrollL2TxnBatch'); } diff --git a/pages/batches/index.tsx b/pages/batches/index.tsx index 176bce9f04..646cfa5368 100644 --- a/pages/batches/index.tsx +++ b/pages/batches/index.tsx @@ -17,6 +17,8 @@ const Batches = dynamic(() => { return import('ui/pages/ZkEvmL2TxnBatches'); case 'zkSync': return import('ui/pages/ZkSyncL2TxnBatches'); + case 'via': + return import('ui/pages/ViaL2TxnBatches'); case 'optimistic': return import('ui/pages/OptimisticL2TxnBatches'); case 'arbitrum': diff --git a/playwright/fixtures/mockEnvs.ts b/playwright/fixtures/mockEnvs.ts index 6860c12ed4..89141eebef 100644 --- a/playwright/fixtures/mockEnvs.ts +++ b/playwright/fixtures/mockEnvs.ts @@ -41,6 +41,11 @@ export const ENVS_MAP: Record> = { [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ], [ 'NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS', 'none' ], ], + ViaRollup: [ + [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'via' ], + [ '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' ], diff --git a/stubs/viaL2.ts b/stubs/viaL2.ts new file mode 100644 index 0000000000..59dee433cf --- /dev/null +++ b/stubs/viaL2.ts @@ -0,0 +1,27 @@ +import type { ViaBatch, ViaBatchesItem } from 'types/api/viaL2'; + +import { TX_HASH } from './tx'; + +export const VIA_L2_TXN_BATCHES_ITEM: ViaBatchesItem = { + commit_transaction_hash: TX_HASH, + commit_transaction_timestamp: '2022-03-17T19:33:04.519145Z', + execute_transaction_hash: TX_HASH, + execute_transaction_timestamp: '2022-03-17T20:49:48.856345Z', + number: 8002, + prove_transaction_hash: TX_HASH, + prove_transaction_timestamp: '2022-03-17T20:49:48.772442Z', + status: 'Executed on L1', + timestamp: '2022-03-17T17:00:11.000000Z', + transactions_count: 1215, +}; + +export const VIA_L2_TXN_BATCH: ViaBatch = { + ...VIA_L2_TXN_BATCHES_ITEM, + start_block_number: 1245209, + end_block_number: 1245490, + l1_gas_price: '4173068062', + l1_transactions_count: 0, + l2_fair_gas_price: '100000000', + l2_transactions_count: 287, + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', +}; diff --git a/tools/preset-sync/index.ts b/tools/preset-sync/index.ts index e1982823e3..a942742027 100755 --- a/tools/preset-sync/index.ts +++ b/tools/preset-sync/index.ts @@ -29,6 +29,7 @@ const PRESETS = { stability_testnet: 'https://stability-testnet.blockscout.com', zkevm: 'https://zkevm.blockscout.com', zksync: 'https://zksync.blockscout.com', + via: 'https://via.blockscout.com', zilliqa_prototestnet: 'https://zilliqa-prototestnet.blockscout.com', zora: 'https://explorer.zora.energy', // main === staging diff --git a/types/api/block.ts b/types/api/block.ts index e582255da3..0dd0ce4ecc 100644 --- a/types/api/block.ts +++ b/types/api/block.ts @@ -6,6 +6,7 @@ import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2'; import type { OptimisticL2BatchDataContainer, OptimisticL2BlobTypeEip4844, OptimisticL2BlobTypeCelestia } from './optimisticL2'; import type { TokenInfo } from './token'; import type { TokenTransfer } from './tokenTransfer'; +import type { ViaBatchesItem } from './viaL2'; import type { ZkSyncBatchesItem } from './zkSyncL2'; export type BlockType = 'block' | 'reorg' | 'uncle'; @@ -59,6 +60,10 @@ export interface Block { zksync?: Omit & { batch_number: number | null; }; + // VIA FIELDS + via?: Omit & { + batch_number: number | null; + }; arbitrum?: ArbitrumBlockData; optimism?: OptimismBlockData; // CELO FIELDS diff --git a/types/api/transaction.ts b/types/api/transaction.ts index a2002d86a2..b83b9a94f0 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -10,6 +10,7 @@ import type { ScrollL2BlockStatus } from './scrollL2'; import type { TokenInfo } from './token'; import type { TokenTransfer } from './tokenTransfer'; import type { TxAction } from './txAction'; +import type { ViaBatchesItem } from './viaL2'; import type { ZkSyncBatchesItem } from './zkSyncL2'; export type TransactionRevertReason = { @@ -92,6 +93,10 @@ export type Transaction = { zksync?: Omit & { batch_number: number | null; }; + // via FIELDS + via?: Omit & { + batch_number: number | null; + }; // Zilliqa fields zilliqa?: { is_scilla: boolean; diff --git a/types/api/viaL2.ts b/types/api/viaL2.ts new file mode 100644 index 0000000000..edb0d81349 --- /dev/null +++ b/types/api/viaL2.ts @@ -0,0 +1,52 @@ +import type { Transaction } from './transaction'; + +export const VIA_L2_TX_BATCH_STATUSES = [ + 'Processed on L2' as const, + 'Sealed on L2' as const, + 'Sent to L1' as const, + 'Validated on L1' as const, + 'Executed on L1' as const, +]; + +export type ViaBatchStatus = typeof VIA_L2_TX_BATCH_STATUSES[number]; + +export interface ViaBatchesItem { + commit_transaction_hash: string | null; + commit_transaction_timestamp: string | null; + execute_transaction_hash: string | null; + execute_transaction_timestamp: string | null; + number: number; + prove_transaction_hash: string | null; + prove_transaction_timestamp: string | null; + status: ViaBatchStatus; + timestamp: string; + transactions_count: number; +} + +export type ViaBatchesResponse = { + items: Array; + next_page_params: { + number: number; + items_count: number; + } | null; +}; + +export interface ViaBatch extends Omit { + start_block_number: number; + end_block_number: number; + l1_gas_price: string; + l1_transactions_count: number; + l2_fair_gas_price: string; + l2_transactions_count: number; + root_hash: string; +} + +export type ViaBatchTxs = { + items: Array; + next_page_params: { + batch_number: string; + block_number: number; + index: number; + items_count: number; + } | null; +}; diff --git a/types/client/rollup.ts b/types/client/rollup.ts index d260ec5be4..5f7dbffca1 100644 --- a/types/client/rollup.ts +++ b/types/client/rollup.ts @@ -6,6 +6,7 @@ export const ROLLUP_TYPES = [ 'shibarium', 'zkEvm', 'zkSync', + 'via', 'scroll', ] as const; diff --git a/ui/address/contract/info/ContractDetailsInfo.pw.tsx b/ui/address/contract/info/ContractDetailsInfo.pw.tsx index 0dc323d70f..47a640c9b1 100644 --- a/ui/address/contract/info/ContractDetailsInfo.pw.tsx +++ b/ui/address/contract/info/ContractDetailsInfo.pw.tsx @@ -31,6 +31,18 @@ test('zkSync contract', async({ render, mockEnvs }) => { await expect(component).toHaveScreenshot(); }); +test('Via contract', async({ render, mockEnvs }) => { + await mockEnvs(ENVS_MAP.viaRollup); + const props = { + data: contractMock.via, + isLoading: false, + addressHash: addressMock.contract.hash, + }; + const component = await render(); + + await expect(component).toHaveScreenshot(); +}); + test('stylus rust contract', async({ render, mockEnvs }) => { await mockEnvs(ENVS_MAP.zkSyncRollup); const props = { diff --git a/ui/address/contract/info/ContractDetailsInfo.tsx b/ui/address/contract/info/ContractDetailsInfo.tsx index 054c6724fb..e096bfbe77 100644 --- a/ui/address/contract/info/ContractDetailsInfo.tsx +++ b/ui/address/contract/info/ContractDetailsInfo.tsx @@ -119,7 +119,7 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => { ) } { data.optimization_runs !== null && !isStylusContract && ( { String(data.optimization_runs) } diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index b7eed13aef..bbd7ebc92e 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -4,6 +4,7 @@ import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; +import { VIA_L2_TX_BATCH_STATUSES } from 'types/api/viaL2'; import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; import { route } from 'nextjs-routes'; @@ -37,6 +38,7 @@ import RawDataSnippet from 'ui/shared/RawDataSnippet'; import StatusTag from 'ui/shared/statusTag/StatusTag'; import Utilization from 'ui/shared/Utilization/Utilization'; import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; +import ViaL2TxnBatchHashesInfo from 'ui/txnBatches/viaL2/ViaL2TxnBatchHashesInfo'; import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo'; import BlockDetailsBaseFeeCelo from './details/BlockDetailsBaseFeeCelo'; @@ -289,8 +291,27 @@ const BlockDetails = ({ query }: Props) => { ) } + + { rollupFeature.isEnabled && rollupFeature.type === 'via' && data.via && !config.UI.views.block.hiddenFields?.batch && ( + <> + + Batch + + + { data.via.batch_number ? + : + Pending } + + + ) } + { !config.UI.views.block.hiddenFields?.L1_status && rollupFeature.isEnabled && - ((rollupFeature.type === 'zkSync' && data.zksync) || (rollupFeature.type === 'arbitrum' && data.arbitrum)) && + ((rollupFeature.type === 'zkSync' && data.zksync) || + (rollupFeature.type === 'arbitrum' && data.arbitrum) || + (rollupFeature.type === 'via' && data.via)) && ( <> { { rollupFeature.type === 'zkSync' && data.zksync && } + { rollupFeature.type === 'via' && data.via && + } { rollupFeature.type === 'arbitrum' && data.arbitrum && ( { { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && } + { rollupFeature.isEnabled && rollupFeature.type === 'via' && data.via && + } + { !isPlaceholderData && } { data.bitcoin_merged_mining_header && ( diff --git a/ui/contractVerification/ContractVerificationForm.pw.tsx b/ui/contractVerification/ContractVerificationForm.pw.tsx index 5ecf615b5b..d688e88060 100644 --- a/ui/contractVerification/ContractVerificationForm.pw.tsx +++ b/ui/contractVerification/ContractVerificationForm.pw.tsx @@ -221,6 +221,20 @@ test('verification of zkSync contract', async({ render, mockEnvs }) => { await expect(component).toHaveScreenshot(); }); +test('verification of viaa contract', async({ render, mockEnvs }) => { + const viaFormConfig: SmartContractVerificationConfig = { + ...formConfig, + verification_options: [ 'standard-input' ], + zk_compiler_versions: [ 'v1.4.1', 'v1.4.0', 'v1.3.23', 'v1.3.22' ], + zk_optimization_modes: [ '0', '1', '2', '3', 's', 'z' ], + }; + + await mockEnvs(ENVS_MAP.viaRollup); + const component = await render(, { hooksConfig }); + + await expect(component).toHaveScreenshot(); +}); + test('verification of stylus rust contract', async({ render, page }) => { const stylusRustFormConfig: SmartContractVerificationConfig = { ...formConfig, diff --git a/ui/contractVerification/methods/ContractVerificationStandardInput.tsx b/ui/contractVerification/methods/ContractVerificationStandardInput.tsx index 53a5036077..1d0b225510 100644 --- a/ui/contractVerification/methods/ContractVerificationStandardInput.tsx +++ b/ui/contractVerification/methods/ContractVerificationStandardInput.tsx @@ -19,7 +19,9 @@ const ContractVerificationStandardInput = ({ config }: { config: SmartContractVe { !config?.is_rust_verifier_microservice_enabled && } - { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && } + { rollupFeature.isEnabled && + (rollupFeature.type === 'zkSync' || rollupFeature.type === 'via') && + } { }, }); + const viaLatestBatchQuery = useApiQuery('general:homepage_via_latest_batch', { + queryOptions: { + placeholderData: 12345, + enabled: rollupFeature.isEnabled && rollupFeature.type === 'via' && config.UI.homepage.stats.includes('latest_batch'), + }, + }); + const arbitrumLatestBatchQuery = useApiQuery('general:homepage_arbitrum_latest_batch', { queryOptions: { placeholderData: 12345, @@ -77,6 +84,8 @@ const Stats = () => { return zkEvmLatestBatchQuery; case 'zkSync': return zkSyncLatestBatchQuery; + case 'via': + return viaLatestBatchQuery; case 'arbitrum': return arbitrumLatestBatchQuery; } diff --git a/ui/pages/ViaL2TxnBatch.pw.tsx b/ui/pages/ViaL2TxnBatch.pw.tsx new file mode 100644 index 0000000000..850bc38121 --- /dev/null +++ b/ui/pages/ViaL2TxnBatch.pw.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import * as viaTxnBatchMock from 'mocks/via/viaTxnBatch'; +import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; +import { test, expect, devices } from 'playwright/lib'; + +import ViaL2TxnBatch from './ViaL2TxnBatch'; + +const batchNumber = String(viaTxnBatchMock.base.number); +const hooksConfig = { + router: { + query: { number: batchNumber }, + }, +}; + +test.beforeEach(async({ mockTextAd, mockApiResponse, mockEnvs }) => { + await mockEnvs(ENVS_MAP.viaRollup); + await mockTextAd(); + await mockApiResponse('general:via_l2_txn_batch', viaTxnBatchMock.base, { pathParams: { number: batchNumber } }); +}); + +test('base view', async({ render }) => { + const component = await render(, { 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(, { hooksConfig }); + await expect(component).toHaveScreenshot(); + }); +}); diff --git a/ui/pages/ViaL2TxnBatch.tsx b/ui/pages/ViaL2TxnBatch.tsx new file mode 100644 index 0000000000..b94f4b8d38 --- /dev/null +++ b/ui/pages/ViaL2TxnBatch.tsx @@ -0,0 +1,105 @@ +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/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 { TX } from 'stubs/tx'; +import { generateListStub } from 'stubs/utils'; +import { VIA_L2_TXN_BATCH } from 'stubs/viaL2'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; +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 ViaL2TxnBatchDetails from 'ui/txnBatches/viaL2/ViaL2TxnBatchDetails'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; + +const TAB_LIST_PROPS = { + marginBottom: 0, + py: 5, + marginTop: -5, +}; + +const TABS_HEIGHT = 80; + +const ViaL2TxnBatch = () => { + const router = useRouter(); + const appProps = useAppContext(); + const number = getQueryParamString(router.query.number); + const tab = getQueryParamString(router.query.tab); + const isMobile = useIsMobile(); + + const batchQuery = useApiQuery('general:via_l2_txn_batch', { + pathParams: { number }, + queryOptions: { + enabled: Boolean(number), + placeholderData: VIA_L2_TXN_BATCH, + }, + }); + + const batchTxsQuery = useQueryWithPages({ + resourceName: 'general:via_l2_txn_batch_txs', + pathParams: { number }, + options: { + enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), + placeholderData: generateListStub<'general:via_l2_txn_batch_txs'>(TX, 50, { next_page_params: { + batch_number: '8122', + block_number: 1338932, + index: 0, + items_count: 50, + } }), + }, + }); + + throwOnAbsentParamError(number); + throwOnResourceLoadError(batchQuery); + + const hasPagination = !isMobile && batchTxsQuery.pagination.isVisible && tab === 'txs'; + + const tabs: Array = React.useMemo(() => ([ + { id: 'index', title: 'Details', component: }, + { + id: 'txs', + title: 'Transactions', + component: , + }, + ].filter(Boolean)), [ batchQuery, batchTxsQuery, 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 ( + <> + + + : null } + stickyEnabled={ hasPagination } + /> + + ); +}; + +export default ViaL2TxnBatch; diff --git a/ui/pages/ViaL2TxnBatches.pw.tsx b/ui/pages/ViaL2TxnBatches.pw.tsx new file mode 100644 index 0000000000..2f93561720 --- /dev/null +++ b/ui/pages/ViaL2TxnBatches.pw.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import * as viaTxnBatchesMock from 'mocks/via/viaTxnBatches'; +import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; +import { test, expect } from 'playwright/lib'; + +import ViaL2TxnBatches from './ViaL2TxnBatches'; + +test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { + test.slow(); + await mockEnvs(ENVS_MAP.viaRollup); + await mockTextAd(); + await mockApiResponse('general:via_l2_txn_batches', viaTxnBatchesMock.baseResponse); + await mockApiResponse('general:via_l2_txn_batches_count', 9927); + + const component = await render(); + await expect(component).toHaveScreenshot({ timeout: 10_000 }); +}); diff --git a/ui/pages/ViaL2TxnBatches.tsx b/ui/pages/ViaL2TxnBatches.tsx new file mode 100644 index 0000000000..eae8c96a5b --- /dev/null +++ b/ui/pages/ViaL2TxnBatches.tsx @@ -0,0 +1,88 @@ +import { Box, Text } from '@chakra-ui/react'; +import React from 'react'; + +import useApiQuery from 'lib/api/useApiQuery'; +import { generateListStub } from 'stubs/utils'; +import { VIA_L2_TXN_BATCHES_ITEM } from 'stubs/viaL2'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +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 ViaTxnBatchesListItem from 'ui/txnBatches/viaL2/ViaTxnBatchesListItem'; +import ViaTxnBatchesTable from 'ui/txnBatches/viaL2/ViaTxnBatchesTable'; + +const ViaL2TxnBatches = () => { + const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ + resourceName: 'general:via_l2_txn_batches', + options: { + placeholderData: generateListStub<'general:via_l2_txn_batches'>( + VIA_L2_TXN_BATCHES_ITEM, + 50, + { + next_page_params: { + items_count: 50, + number: 9045200, + }, + }, + ), + }, + }); + + const countersQuery = useApiQuery('general:via_l2_txn_batches_count', { + queryOptions: { + placeholderData: 5231746, + }, + }); + + const content = data?.items ? ( + <> + + { data.items.map(((item, index) => ( + + ))) } + + + + + + ) : null; + + const text = (() => { + if (countersQuery.isError || isError || !data?.items.length) { + return null; + } + + return ( + + Txn batch + #{ data.items[0].number } to + #{ data.items[data.items.length - 1].number } + (total of { countersQuery.data?.toLocaleString() } batches) + + ); + })(); + + const actionBar = ; + + return ( + <> + + + { content } + + + ); +}; + +export default ViaL2TxnBatches; diff --git a/ui/shared/statusTag/ViaL2TxnBatchStatus.tsx b/ui/shared/statusTag/ViaL2TxnBatchStatus.tsx new file mode 100644 index 0000000000..e3e441d1dc --- /dev/null +++ b/ui/shared/statusTag/ViaL2TxnBatchStatus.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import type { ViaBatchStatus } from 'types/api/viaL2'; + +import type { StatusTagType } from './StatusTag'; +import StatusTag from './StatusTag'; + +export interface Props { + status: ViaBatchStatus; + isLoading?: boolean; +} + +const ViaL2TxnBatchStatus = ({ status, isLoading }: Props) => { + let type: StatusTagType; + + switch (status) { + case 'Executed on L1': + type = 'ok'; + break; + default: + type = 'pending'; + break; + } + + return ; +}; + +export default ViaL2TxnBatchStatus; diff --git a/ui/tx/details/TxInfo.tsx b/ui/tx/details/TxInfo.tsx index cefa3f7733..ed191ff3d4 100644 --- a/ui/tx/details/TxInfo.tsx +++ b/ui/tx/details/TxInfo.tsx @@ -12,6 +12,7 @@ import React from 'react'; 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 { VIA_L2_TX_BATCH_STATUSES } from 'types/api/viaL2'; import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; import { route } from 'nextjs-routes'; @@ -60,6 +61,7 @@ import TxRevertReason from 'ui/tx/details/TxRevertReason'; import TxAllowedPeekers from 'ui/tx/TxAllowedPeekers'; import TxExternalTxs from 'ui/tx/TxExternalTxs'; import TxSocketAlert from 'ui/tx/TxSocketAlert'; +import ViaL2TxnBatchHashesInfo from 'ui/txnBatches/viaL2/ViaL2TxnBatchHashesInfo'; import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo'; import TxDetailsInterop from './TxDetailsInterop'; @@ -185,7 +187,11 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { > { rollupFeature.isEnabled && - (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum' || rollupFeature.type === 'scroll') ? + (rollupFeature.type === 'zkEvm' || + rollupFeature.type === 'zkSync' || + rollupFeature.type === 'via' || + rollupFeature.type === 'arbitrum' || + rollupFeature.type === 'scroll') ? 'L2 status and method' : 'Status and method' } @@ -293,6 +299,20 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { ) } + { data.via && !config.UI.views.tx.hiddenFields?.L1_status && ( + <> + + L1 status + + + + + + ) } + { ) } + { data.via && !config.UI.views.tx.hiddenFields?.batch && ( + <> + + Batch + + + { data.via.batch_number ? ( + + ) : Pending } + + + ) } + { data.arbitrum && !config.UI.views.tx.hiddenFields?.batch && ( <> { ) } { data.zksync && } + { data.via && } ); diff --git a/ui/txnBatches/viaL2/ViaL2TxnBatchDetails.tsx b/ui/txnBatches/viaL2/ViaL2TxnBatchDetails.tsx new file mode 100644 index 0000000000..a13b146b7b --- /dev/null +++ b/ui/txnBatches/viaL2/ViaL2TxnBatchDetails.tsx @@ -0,0 +1,168 @@ +import { Grid, GridItem, Text } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import BigNumber from 'bignumber.js'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import { VIA_L2_TX_BATCH_STATUSES, type ViaBatch } from 'types/api/viaL2'; + +import { route } from 'nextjs-routes'; + +import type { ResourceError } from 'lib/api/resources'; +import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; +import { currencyUnits } from 'lib/units'; +import { CollapsibleDetails } from 'toolkit/chakra/collapsible'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { WEI, WEI_IN_GWEI } from 'toolkit/utils/consts'; +import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; +import CopyToClipboard from 'ui/shared/CopyToClipboard'; +import DataFetchAlert from 'ui/shared/DataFetchAlert'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; +import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; +import PrevNext from 'ui/shared/PrevNext'; +import TruncatedValue from 'ui/shared/TruncatedValue'; +import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; + +import ViaL2TxnBatchHashesInfo from './ViaL2TxnBatchHashesInfo'; + +interface Props { + query: UseQueryResult; +} + +const ViaL2TxnBatchDetails = ({ 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 ; + } + + if (!data) { + return null; + } + + const txNum = data.l2_transactions_count + data.l1_transactions_count; + + return ( + + + Txn batch number + + + + { data.number } + + + + + + Status + + + + + + + Timestamp + + + { data.timestamp ? : 'Undefined' } + + + + Transactions + + + + + { txNum } transaction{ txNum === 1 ? '' : 's' } + + + + + + + + + + + + + Root hash + + + + + + + + L1 gas price + + + { BigNumber(data.l1_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether } + ({ BigNumber(data.l1_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei }) + + + + L2 fair gas price + + + { BigNumber(data.l2_fair_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether } + ({ BigNumber(data.l2_fair_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei }) + + + + ); +}; + +export default ViaL2TxnBatchDetails; diff --git a/ui/txnBatches/viaL2/ViaL2TxnBatchHashesInfo.tsx b/ui/txnBatches/viaL2/ViaL2TxnBatchHashesInfo.tsx new file mode 100644 index 0000000000..861ad68be6 --- /dev/null +++ b/ui/txnBatches/viaL2/ViaL2TxnBatchHashesInfo.tsx @@ -0,0 +1,111 @@ +import { Flex } from '@chakra-ui/react'; +import React from 'react'; + +import type { ViaBatch } from 'types/api/viaL2'; + +import { Skeleton } from 'toolkit/chakra/skeleton'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; +import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; + +interface Props { + isLoading: boolean; + data: Pick< + ViaBatch, + 'commit_transaction_hash' | + 'commit_transaction_timestamp' | + 'prove_transaction_hash' | + 'prove_transaction_timestamp' | + 'execute_transaction_hash' | + 'execute_transaction_timestamp' + >; +} + +const ViaL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => { + return ( + <> + + Commit tx hash + + + { data.commit_transaction_hash ? ( + <> + + { data.commit_transaction_timestamp && ( + + + + ) } + + ) : Pending } + + + + Prove tx hash + + + { data.prove_transaction_hash ? ( + <> + + { data.prove_transaction_timestamp && ( + + + + ) } + + ) : Pending } + + + + Execute tx hash + + + { data.prove_transaction_hash && data.execute_transaction_hash ? ( + <> + + { data.execute_transaction_timestamp && ( + + + + ) } + + ) : Pending } + + + ); +}; + +export default React.memo(ViaL2TxnBatchHashesInfo); diff --git a/ui/txnBatches/viaL2/ViaTxnBatchesListItem.tsx b/ui/txnBatches/viaL2/ViaTxnBatchesListItem.tsx new file mode 100644 index 0000000000..d2f7dc21bd --- /dev/null +++ b/ui/txnBatches/viaL2/ViaTxnBatchesListItem.tsx @@ -0,0 +1,93 @@ +import { Text } from '@chakra-ui/react'; +import React from 'react'; + +import type { ViaBatchesItem } from 'types/api/viaL2'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; +import { Link } from 'toolkit/chakra/link'; +import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; +import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; +import ViaL2TxnBatchStatus from 'ui/shared/statusTag/ViaL2TxnBatchStatus'; +import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; + +const rollupFeature = config.features.rollup; + +type Props = { item: ViaBatchesItem; isLoading?: boolean }; + +const ViaTxnBatchesListItem = ({ item, isLoading }: Props) => { + if (!rollupFeature.isEnabled || rollupFeature.type !== 'via' || item.number === 0) { + return null; + } + + return ( + + + Batch # + + + + + Status + + + + + Age + + + + + Txn count + + + { item.transactions_count } + + + + Commit tx + + { item.commit_transaction_hash ? ( + + ) : Pending } + + + Prove tx + + { item.prove_transaction_hash ? ( + + ) : Pending } + + + + ); +}; + +export default ViaTxnBatchesListItem; diff --git a/ui/txnBatches/viaL2/ViaTxnBatchesTable.tsx b/ui/txnBatches/viaL2/ViaTxnBatchesTable.tsx new file mode 100644 index 0000000000..6a8495e0b6 --- /dev/null +++ b/ui/txnBatches/viaL2/ViaTxnBatchesTable.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +import type { ViaBatchesItem } from 'types/api/viaL2'; + +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; + +import ViaTxnBatchesTableItem from './ViaTxnBatchesTableItem'; + +type Props = { + items: Array; + top: number; + isLoading?: boolean; +}; + +const ViaTxnBatchesTable = ({ items, top, isLoading }: Props) => { + return ( + + + + Batch # + Status + Age + Txn count + Commit tx + Prove tx + + + + { items.map((item, index) => ( + + )) } + + + ); +}; + +export default ViaTxnBatchesTable; diff --git a/ui/txnBatches/viaL2/ViaTxnBatchesTableItem.tsx b/ui/txnBatches/viaL2/ViaTxnBatchesTableItem.tsx new file mode 100644 index 0000000000..83e6d08a12 --- /dev/null +++ b/ui/txnBatches/viaL2/ViaTxnBatchesTableItem.tsx @@ -0,0 +1,83 @@ +import { Text } from '@chakra-ui/react'; +import React from 'react'; + +import type { ViaBatchesItem } from 'types/api/viaL2'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; +import { Link } from 'toolkit/chakra/link'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; +import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; +import ViaL2TxnBatchStatus from 'ui/shared/statusTag/ViaL2TxnBatchStatus'; +import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; + +const rollupFeature = config.features.rollup; + +type Props = { item: ViaBatchesItem; isLoading?: boolean }; + +const ViaTxnBatchesTableItem = ({ item, isLoading }: Props) => { + if (!rollupFeature.isEnabled || rollupFeature.type !== 'via' || item.number === 0) { + return null; + } + + return ( + + + + + + + + + + + + + { item.transactions_count } + + + + { item.commit_transaction_hash ? ( + + ) : Pending } + + + { item.prove_transaction_hash ? ( + + ) : Pending } + + + ); +}; + +export default ViaTxnBatchesTableItem;