diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index a0c346af6b..6c6ecb3f3d 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -26,6 +26,7 @@ on: - optimism_celestia - optimism_sepolia - polygon + - rari_testnet - rootstock - shibarium - scroll_sepolia diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 64a8f6b1f3..8cf832c905 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -374,6 +374,7 @@ "optimism_celestia", "optimism_sepolia", "polygon", + "rari_testnet", "rootstock_testnet", "shibarium", "stability_testnet", diff --git a/configs/app/features/rollup.ts b/configs/app/features/rollup.ts index e8f5cac92f..0e4766623a 100644 --- a/configs/app/features/rollup.ts +++ b/configs/app/features/rollup.ts @@ -23,6 +23,11 @@ const config: Feature<{ outputRootsEnabled: boolean; L2WithdrawalUrl: string | undefined; parentChainName: string | undefined; + DA: { + celestia: { + namespace: string | undefined; + }; + }; }> = (() => { if (type && L1BaseUrl) { return Object.freeze({ @@ -36,6 +41,11 @@ const config: Feature<{ homepage: { showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', }, + DA: { + celestia: { + namespace: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE') : undefined, + }, + }, }); } diff --git a/configs/envs/.env.rari_testnet b/configs/envs/.env.rari_testnet new file mode 100644 index 0000000000..cdcaaaa97c --- /dev/null +++ b/configs/envs/.env.rari_testnet @@ -0,0 +1,40 @@ +# Set of ENVs for Rari Testnet network explorer +# https://rari-testnet.cloud.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=rari_testnet" + +# 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_AD_BANNER_PROVIDER=slise +NEXT_PUBLIC_AD_TEXT_PROVIDER=coinzilla +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=rari-testnet.cloud.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_COLOR_THEME_DEFAULT=light +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363 +NEXT_PUBLIC_HAS_BEACON_CHAIN=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=radial-gradient(farthest-corner at 0% 0%, rgba(183, 148, 244, 0.80) 0%, rgba(0, 163, 196, 0.80) 100%) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255,255,255) +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS=[] +NEXT_PUBLIC_NAVIGATION_LAYOUT=vertical +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ID=1 +NEXT_PUBLIC_NETWORK_NAME=Rari Testnet +NEXT_PUBLIC_NETWORK_SHORT_NAME=Rari Testnet +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=false +NEXT_PUBLIC_OTHER_LINKS=[] +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://arbitrum-sepolia.blockscout.com/ +NEXT_PUBLIC_ROLLUP_TYPE=arbitrum +NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=false +NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=jazzicon +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE=0x00000000000000000000000000000000000000ca1de12a9905be97beaf \ No newline at end of file diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index bed33149c7..f759ad22ef 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -321,6 +321,16 @@ const rollupSchema = yup value => value === undefined, ), }), + NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE: yup + .string() + .min(60) + .max(60) + .matches(regexp.HEX_REGEXP_WITH_0X) + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: (value: string) => value === 'arbitrum', + then: (schema) => schema, + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE can only be used if NEXT_PUBLIC_ROLLUP_TYPE is set to \'arbitrum\' '), + }), }); const celoSchema = yup diff --git a/deploy/tools/envs-validator/test/.env.arbitrum b/deploy/tools/envs-validator/test/.env.arbitrum index ee0ce91f00..9559dd3508 100644 --- a/deploy/tools/envs-validator/test/.env.arbitrum +++ b/deploy/tools/envs-validator/test/.env.arbitrum @@ -1,4 +1,5 @@ NEXT_PUBLIC_ROLLUP_TYPE=arbitrum NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true -NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME=DuckChain \ No newline at end of file +NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME=DuckChain +NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE=0x00000000000000000000000000000000000000ca1de12a9905be97beaf \ No newline at end of file diff --git a/docs/ENVS.md b/docs/ENVS.md index a2272bbf2a..9ab396a846 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -457,6 +457,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ | | NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `false` | `true` | v1.37.0+ | | NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME | `string` | Set to customize L1 transaction status labels in the UI (e.g., "Sent to "). This setting is applicable only for Arbitrum-based chains. | - | - | `DuckChain` | v1.37.0+ | +| NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE | `string` | Hex-string for creating a link to the transaction batch on the Seleneium explorer. "0x"-format and 60 symbol length. Available only for Arbitrum roll-ups. | - | - | `0x00000000000000000000000000000000000000ca1de12a9905be97beaf` | v1.38.0+ |   diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 2c9f4ca1ef..8bf3376752 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -819,6 +819,11 @@ export const RESOURCES = { pathParams: [ 'number' as const ], }, + optimistic_l2_txn_batch_celestia: { + path: '/api/v2/optimism/batches/da/celestia/:height/:commitment', + pathParams: [ 'height' as const, 'commitment' as const ], + }, + optimistic_l2_txn_batch_txs: { path: '/api/v2/transactions/optimism-batch/:number', pathParams: [ 'number' as const ], @@ -904,6 +909,11 @@ export const RESOURCES = { pathParams: [ 'number' as const ], }, + arbitrum_l2_txn_batch_celestia: { + path: '/api/v2/arbitrum/batches/da/celestia/:height/:commitment', + pathParams: [ 'height' as const, 'commitment' as const ], + }, + arbitrum_l2_txn_batch_txs: { path: '/api/v2/transactions/arbitrum-batch/:number', pathParams: [ 'number' as const ], @@ -1339,6 +1349,7 @@ Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse : Q extends 'optimistic_l2_txn_batches_count' ? number : Q extends 'optimistic_l2_txn_batch' ? OptimismL2TxnBatch : +Q extends 'optimistic_l2_txn_batch_celestia' ? OptimismL2TxnBatch : Q extends 'optimistic_l2_txn_batch_txs' ? OptimismL2BatchTxs : Q extends 'optimistic_l2_txn_batch_blocks' ? OptimismL2BatchBlocks : Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse : @@ -1373,6 +1384,7 @@ Q extends 'arbitrum_l2_messages_count' ? number : Q extends 'arbitrum_l2_txn_batches' ? ArbitrumL2TxnBatchesResponse : Q extends 'arbitrum_l2_txn_batches_count' ? number : Q extends 'arbitrum_l2_txn_batch' ? ArbitrumL2TxnBatch : +Q extends 'arbitrum_l2_txn_batch_celestia' ? ArbitrumL2TxnBatch : Q extends 'arbitrum_l2_txn_batch_txs' ? ArbitrumL2BatchTxs : Q extends 'arbitrum_l2_txn_batch_blocks' ? ArbitrumL2BatchBlocks : Q extends 'zkevm_l2_deposits' ? ZkEvmL2DepositsResponse : diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index 4535257365..e26c186eab 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -43,6 +43,7 @@ const OG_TYPE_DICT: Record = { '/dispute-games': 'Root page', '/batches': 'Root page', '/batches/[number]': 'Regular page', + '/batches/celestia/[height]/[commitment]': 'Regular page', '/blobs/[hash]': 'Regular page', '/ops': 'Root page', '/op/[hash]': 'Regular page', diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index a8069e0f28..536778d87e 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -46,6 +46,7 @@ const TEMPLATE_MAP: Record = { '/dispute-games': DEFAULT_TEMPLATE, '/batches': DEFAULT_TEMPLATE, '/batches/[number]': DEFAULT_TEMPLATE, + '/batches/celestia/[height]/[commitment]': DEFAULT_TEMPLATE, '/blobs/[hash]': DEFAULT_TEMPLATE, '/ops': DEFAULT_TEMPLATE, '/op/[hash]': DEFAULT_TEMPLATE, diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 46eedf1d30..33a81ff3ce 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -43,6 +43,7 @@ const TEMPLATE_MAP: Record = { '/dispute-games': '%network_name% dispute games', '/batches': '%network_name% txn batches', '/batches/[number]': '%network_name% L2 txn batch %number%', + '/batches/celestia/[height]/[commitment]': '%network_name% L2 txn batch %height% %commitment%', '/blobs/[hash]': '%network_name% blob %hash% details', '/ops': 'User operations on %network_name% - %network_name% explorer', '/op/[hash]': '%network_name% user operation %hash%', diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index e361248bbf..db3fc08073 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -41,6 +41,7 @@ export const PAGE_TYPE_DICT: Record = { '/dispute-games': 'Dispute games', '/batches': 'Txn batches', '/batches/[number]': 'L2 txn batch details', + '/batches/celestia/[height]/[commitment]': 'L2 txn batch details', '/blobs/[hash]': 'Blob details', '/ops': 'User operations', '/op/[hash]': 'User operation details', diff --git a/lib/regexp.ts b/lib/regexp.ts index ab2b3288e3..6667a6e064 100644 --- a/lib/regexp.ts +++ b/lib/regexp.ts @@ -3,6 +3,7 @@ export const URL_PREFIX = /^https?:\/\//i; export const IPFS_PREFIX = /^ipfs:\/\//i; export const HEX_REGEXP = /^(?:0x)?[\da-fA-F]+$/; +export const HEX_REGEXP_WITH_0X = /^0x[\da-fA-F]+$/; export const FILE_EXTENSION = /\.([\da-z]+)$/i; diff --git a/mocks/arbitrum/txnBatch.ts b/mocks/arbitrum/txnBatch.ts index 5d163ba6bc..5e4acdfbc9 100644 --- a/mocks/arbitrum/txnBatch.ts +++ b/mocks/arbitrum/txnBatch.ts @@ -38,3 +38,16 @@ export const batchDataAnytrust: ArbitrumL2TxnBatch = { ], }, }; + +export const batchDataCelestia: ArbitrumL2TxnBatch = { + ...finalized, + after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', + before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', + start_block: 1245209, + end_block: 1245490, + data_availability: { + batch_data_container: 'in_celestia', + height: 4520041, + transaction_commitment: '0x3ebe5a43f47fbf69db003e543bb27e4875929ede2fa9a25d09f0bd082d5d20f0', + }, +}; diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index 3927590800..394fcc5ebe 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -123,6 +123,16 @@ export const batch: GetServerSideProps = async(context) => { return base(context); }; +export const batchCelestia: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && (rollupFeature.type === 'arbitrum' || rollupFeature.type === 'optimistic'))) { + return { + notFound: true, + }; + } + + return base(context); +}; + export const marketplace = async (context: GetServerSidePropsContext): Promise>> => { if (!config.features.marketplace.isEnabled) { diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index 185a221d3c..3104e83f18 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -32,6 +32,7 @@ declare module "nextjs-routes" { | StaticRoute<"/apps"> | StaticRoute<"/auth/profile"> | DynamicRoute<"/batches/[number]", { "number": string }> + | DynamicRoute<"/batches/celestia/[height]/[commitment]", { "height": string; "commitment": string }> | StaticRoute<"/batches"> | DynamicRoute<"/blobs/[hash]", { "hash": string }> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> diff --git a/pages/batches/celestia/[height]/[commitment].tsx b/pages/batches/celestia/[height]/[commitment].tsx new file mode 100644 index 0000000000..49db9b9d09 --- /dev/null +++ b/pages/batches/celestia/[height]/[commitment].tsx @@ -0,0 +1,36 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import type { Props } from 'nextjs/getServerSideProps'; +import PageNextJs from 'nextjs/PageNextJs'; + +import config from 'configs/app'; + +const rollupFeature = config.features.rollup; + +const Batch = dynamic(() => { + if (!rollupFeature.isEnabled) { + throw new Error('Rollup feature is not enabled.'); + } + + switch (rollupFeature.type) { + case 'arbitrum': + return import('ui/pages/ArbitrumL2TxnBatch'); + case 'optimistic': + return import('ui/pages/OptimisticL2TxnBatch'); + } + throw new Error('Celestia txn batches feature is not enabled.'); +}, { ssr: false }); + +const Page: NextPage = (props: Props) => { + return ( + + + + ); +}; + +export default Page; + +export { batchCelestia as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright/fixtures/mockEnvs.ts b/playwright/fixtures/mockEnvs.ts index 35de51e324..2f1cbd5d9d 100644 --- a/playwright/fixtures/mockEnvs.ts +++ b/playwright/fixtures/mockEnvs.ts @@ -26,6 +26,7 @@ export const ENVS_MAP: Record> = { [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'arbitrum' ], [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ], [ 'NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME', 'DuckChain' ], + [ 'NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE', '0x1234' ], ], shibariumRollup: [ [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ], diff --git a/tools/preset-sync/index.ts b/tools/preset-sync/index.ts index 8f17a10b8d..4390737c25 100755 --- a/tools/preset-sync/index.ts +++ b/tools/preset-sync/index.ts @@ -19,6 +19,7 @@ const PRESETS = { optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com', optimism_sepolia: 'https://optimism-sepolia.blockscout.com', polygon: 'https://polygon.blockscout.com', + rari_testnet: 'https://rari-testnet.cloud.blockscout.com', rootstock_testnet: 'https://rootstock-testnet.blockscout.com', scroll_sepolia: 'https://scroll-sepolia.blockscout.com', shibarium: 'https://www.shibariumscan.io', diff --git a/types/api/arbitrumL2.ts b/types/api/arbitrumL2.ts index 4bf19ed35e..eed7033492 100644 --- a/types/api/arbitrumL2.ts +++ b/types/api/arbitrumL2.ts @@ -74,8 +74,14 @@ export type ArbitrumL2TxnBatchDAAnytrust = { }>; }; -export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | { - batch_data_container: Exclude; +export type ArbitrumL2TxnBatchDACelestia = { + batch_data_container: 'in_celestia'; + height: number; + transaction_commitment: string; +}; + +export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | ArbitrumL2TxnBatchDACelestia | { + batch_data_container: Exclude; }; export type ArbitrumL2TxnBatch = { diff --git a/ui/pages/ArbitrumL2TxnBatch.pw.tsx b/ui/pages/ArbitrumL2TxnBatch.pw.tsx index 1057ae2744..c2b153d61b 100644 --- a/ui/pages/ArbitrumL2TxnBatch.pw.tsx +++ b/ui/pages/ArbitrumL2TxnBatch.pw.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { batchData, batchDataAnytrust } from 'mocks/arbitrum/txnBatch'; +import { batchData, batchDataAnytrust, batchDataCelestia } from 'mocks/arbitrum/txnBatch'; import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; import { test, expect, devices } from 'playwright/lib'; @@ -31,6 +31,13 @@ test('with anytrust DA', async({ render, mockApiResponse }) => { await expect(component).toHaveScreenshot(); }); +test('with celestia DA', async({ render, mockApiResponse }) => { + await mockApiResponse('arbitrum_l2_txn_batch', batchDataCelestia, { pathParams: { number: batchNumber } }); + const component = await render(, { hooksConfig }); + await component.getByText('Show data availability info').click(); + await expect(component).toHaveScreenshot(); +}); + test.describe('mobile', () => { test.use({ viewport: devices['iPhone 13 Pro'].viewport }); test('base view', async({ render, mockApiResponse }) => { @@ -45,4 +52,12 @@ test.describe('mobile', () => { await component.getByText('Show data availability info').click(); await expect(component).toHaveScreenshot(); }); + + test('with celestia DA', async({ render, mockApiResponse, page }) => { + await mockApiResponse('arbitrum_l2_txn_batch', batchDataCelestia, { pathParams: { number: batchNumber } }); + const component = await render(, { hooksConfig }); + await component.getByText('Show data availability info').click(); + await page.mouse.move(0, 0); + await expect(component).toHaveScreenshot(); + }); }); diff --git a/ui/pages/ArbitrumL2TxnBatch.tsx b/ui/pages/ArbitrumL2TxnBatch.tsx index 6684df430f..0babe5ac4d 100644 --- a/ui/pages/ArbitrumL2TxnBatch.tsx +++ b/ui/pages/ArbitrumL2TxnBatch.tsx @@ -3,13 +3,11 @@ import React from 'react'; import type { RoutedTab } from 'ui/shared/Tabs/types'; -import useApiQuery from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import useIsMobile from 'lib/hooks/useIsMobile'; import getQueryParamString from 'lib/router/getQueryParamString'; -import { ARBITRUM_L2_TXN_BATCH } from 'stubs/arbitrumL2'; import { BLOCK } from 'stubs/block'; import { TX } from 'stubs/tx'; import { generateListStub } from 'stubs/utils'; @@ -21,6 +19,7 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import ArbitrumL2TxnBatchDetails from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails'; +import useBatchQuery from 'ui/txnBatches/arbitrumL2/useBatchQuery'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { @@ -35,20 +34,16 @@ const ArbitrumL2TxnBatch = () => { const router = useRouter(); const appProps = useAppContext(); const number = getQueryParamString(router.query.number); + const height = getQueryParamString(router.query.height); + const commitment = getQueryParamString(router.query.commitment); const tab = getQueryParamString(router.query.tab); const isMobile = useIsMobile(); - const batchQuery = useApiQuery('arbitrum_l2_txn_batch', { - pathParams: { number }, - queryOptions: { - enabled: Boolean(number), - placeholderData: ARBITRUM_L2_TXN_BATCH, - }, - }); + const batchQuery = useBatchQuery(); const batchTxsQuery = useQueryWithPages({ resourceName: 'arbitrum_l2_txn_batch_txs', - pathParams: { number }, + pathParams: { number: String(batchQuery.data?.number) }, options: { enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), placeholderData: generateListStub<'arbitrum_l2_txn_batch_txs'>(TX, 50, { next_page_params: { @@ -62,7 +57,7 @@ const ArbitrumL2TxnBatch = () => { const batchBlocksQuery = useQueryWithPages({ resourceName: 'arbitrum_l2_txn_batch_blocks', - pathParams: { number }, + pathParams: { number: String(batchQuery.data?.number) }, options: { enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'), placeholderData: generateListStub<'arbitrum_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { @@ -73,7 +68,7 @@ const ArbitrumL2TxnBatch = () => { }, }); - throwOnAbsentParamError(number); + throwOnAbsentParamError(number || (height && commitment)); throwOnResourceLoadError(batchQuery); let pagination; @@ -117,8 +112,9 @@ const ArbitrumL2TxnBatch = () => { <> { batchQuery.isPlaceholderData ? : ( diff --git a/ui/pages/ArbitrumL2TxnBatches.pw.tsx b/ui/pages/ArbitrumL2TxnBatches.pw.tsx index f1c0a06d06..72ddc12235 100644 --- a/ui/pages/ArbitrumL2TxnBatches.pw.tsx +++ b/ui/pages/ArbitrumL2TxnBatches.pw.tsx @@ -2,11 +2,11 @@ import React from 'react'; import * as arbitrumTxnBatchesMock from 'mocks/arbitrum/txnBatches'; import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; +import { test, expect, devices } from 'playwright/lib'; import ArbitrumL2TxnBatches from './ArbitrumL2TxnBatches'; -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { +test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { test.slow(); await mockEnvs(ENVS_MAP.arbitrumRollup); await mockTextAd(); @@ -16,3 +16,17 @@ test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse const component = await render(); 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.arbitrumRollup); + await mockTextAd(); + await mockApiResponse('arbitrum_l2_txn_batches', arbitrumTxnBatchesMock.baseResponse); + await mockApiResponse('arbitrum_l2_txn_batches_count', 9927); + + const component = await render(); + await expect(component).toHaveScreenshot(); + }); +}); diff --git a/ui/pages/OptimisticL2TxnBatch.tsx b/ui/pages/OptimisticL2TxnBatch.tsx index 0fc369a716..f99545a1a2 100644 --- a/ui/pages/OptimisticL2TxnBatch.tsx +++ b/ui/pages/OptimisticL2TxnBatch.tsx @@ -3,14 +3,12 @@ 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 { L2_TXN_BATCH } from 'stubs/L2'; import { TX } from 'stubs/tx'; import { generateListStub } from 'stubs/utils'; import BlocksContent from 'ui/blocks/BlocksContent'; @@ -21,6 +19,7 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import OptimisticL2TxnBatchDetails from 'ui/txnBatches/optimisticL2/OptimisticL2TxnBatchDetails'; +import useBatchQuery from 'ui/txnBatches/optimisticL2/useBatchQuery'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { @@ -35,20 +34,16 @@ const OptimisticL2TxnBatch = () => { const router = useRouter(); const appProps = useAppContext(); const number = getQueryParamString(router.query.number); + const height = getQueryParamString(router.query.height); + const commitment = getQueryParamString(router.query.commitment); const tab = getQueryParamString(router.query.tab); const isMobile = useIsMobile(); - const batchQuery = useApiQuery('optimistic_l2_txn_batch', { - pathParams: { number }, - queryOptions: { - enabled: Boolean(number), - placeholderData: L2_TXN_BATCH, - }, - }); + const batchQuery = useBatchQuery(); const batchTxsQuery = useQueryWithPages({ resourceName: 'optimistic_l2_txn_batch_txs', - pathParams: { number }, + pathParams: { number: String(batchQuery.data?.internal_id) }, options: { enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.internal_id && tab === 'txs'), placeholderData: generateListStub<'optimistic_l2_txn_batch_txs'>(TX, 50, { next_page_params: { @@ -61,7 +56,7 @@ const OptimisticL2TxnBatch = () => { const batchBlocksQuery = useQueryWithPages({ resourceName: 'optimistic_l2_txn_batch_blocks', - pathParams: { number }, + pathParams: { number: String(batchQuery.data?.internal_id) }, options: { enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.internal_id && tab === 'blocks'), placeholderData: generateListStub<'optimistic_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { @@ -71,7 +66,7 @@ const OptimisticL2TxnBatch = () => { }, }); - throwOnAbsentParamError(number); + throwOnAbsentParamError(number || (height && commitment)); throwOnResourceLoadError(batchQuery); let pagination; @@ -115,8 +110,9 @@ const OptimisticL2TxnBatch = () => { <> { batchQuery.isPlaceholderData ? : ( diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_base-view-1.png index d767e3c628..4878364e3c 100644 Binary files a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_base-view-1.png and b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_base-view-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-base-view-1.png index f4748e9622..0ea744af74 100644 Binary files a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-anytrust-DA-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-anytrust-DA-1.png index ec2e4cc770..016c227106 100644 Binary files a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-anytrust-DA-1.png and b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-anytrust-DA-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-celestia-DA-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-celestia-DA-1.png new file mode 100644 index 0000000000..2d1d21fc81 Binary files /dev/null and b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_mobile-with-celestia-DA-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-anytrust-DA-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-anytrust-DA-1.png index acb0a4b1b4..b74cfea892 100644 Binary files a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-anytrust-DA-1.png and b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-anytrust-DA-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-celestia-DA-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-celestia-DA-1.png new file mode 100644 index 0000000000..d796aa1763 Binary files /dev/null and b/ui/pages/__screenshots__/ArbitrumL2TxnBatch.pw.tsx_default_with-celestia-DA-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-1.png new file mode 100644 index 0000000000..b0bf1b0295 Binary files /dev/null and b/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index bcef5677d3..0000000000 Binary files a/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_mobile-base-view-1.png new file mode 100644 index 0000000000..2329a420a6 Binary files /dev/null and b/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index d091879280..0000000000 Binary files a/ui/pages/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx b/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx index ac7b789bfe..6e25e00162 100644 --- a/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx +++ b/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx @@ -19,16 +19,16 @@ const ArbitrumL2TxnBatchDA = ({ dataContainer, isLoading }: Props) => { switch (dataContainer) { case 'in_blob4844': - text = 'blob'; + text = 'Blob'; break; case 'in_anytrust': - text = 'anytrust'; + text = 'AnyTrust'; break; case 'in_calldata': - text = 'calldata'; + text = 'Calldata'; break; case 'in_celestia': - text = 'celestia'; + text = 'Celestia'; break; default: text = ''; diff --git a/ui/shared/batch/CeleniumLink.tsx b/ui/shared/batch/CeleniumLink.tsx new file mode 100644 index 0000000000..e48186ee23 --- /dev/null +++ b/ui/shared/batch/CeleniumLink.tsx @@ -0,0 +1,34 @@ +import { Flex, Icon } from '@chakra-ui/react'; +import React from 'react'; + +// eslint-disable-next-line no-restricted-imports +import celeniumIcon from 'icons/brands/celenium.svg'; +import hexToBase64 from 'lib/hexToBase64'; +import LinkExternal from 'ui/shared/links/LinkExternal'; + +interface Props { + commitment: string; + namespace: string; + height: number; +} + +function getCeleniumUrl(props: Props) { + const url = new URL('https://mocha.celenium.io/blob'); + + url.searchParams.set('commitment', hexToBase64(props.commitment)); + url.searchParams.set('hash', hexToBase64(props.namespace)); + url.searchParams.set('height', String(props.height)); + + return url.toString(); +} + +const CeleniumLink = (props: Props) => { + return ( + + + Blob page + + ); +}; + +export default React.memo(CeleniumLink); diff --git a/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails.tsx b/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails.tsx index 5e0b895855..05fb41b2c6 100644 --- a/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails.tsx +++ b/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails.tsx @@ -23,7 +23,8 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import LinkInternal from 'ui/shared/links/LinkInternal'; import PrevNext from 'ui/shared/PrevNext'; -import ArbitrumL2TxnBatchDetailsDA from './ArbitrumL2TxnBatchDetailsDA'; +import ArbitrumL2TxnBatchDetailsAnyTrustDA from './ArbitrumL2TxnBatchDetailsAnyTrustDA'; +import ArbitrumL2TxnBatchDetailsCelestiaDA from './ArbitrumL2TxnBatchDetailsCelestiaDA'; interface Props { query: UseQueryResult; } @@ -181,11 +182,11 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => { > Before acc - + - + { > After acc - + - + - { data.data_availability.batch_data_container === 'in_anytrust' && ( + { (data.data_availability.batch_data_container === 'in_anytrust' || data.data_availability.batch_data_container === 'in_celestia') && ( <> { /* CUT */ } @@ -224,7 +225,12 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => { <> - + { data.data_availability.batch_data_container === 'in_anytrust' && ( + + ) } + { data.data_availability.batch_data_container === 'in_celestia' && ( + + ) } ) } diff --git a/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsDA.tsx b/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsAnyTrustDA.tsx similarity index 84% rename from ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsDA.tsx rename to ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsAnyTrustDA.tsx index 2f1c712c31..a5c37d094c 100644 --- a/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsDA.tsx +++ b/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsAnyTrustDA.tsx @@ -12,10 +12,10 @@ import IconSvg from 'ui/shared/IconSvg'; import TextSeparator from 'ui/shared/TextSeparator'; type Props = { - dataAvailability: ArbitrumL2TxnBatchDAAnytrust; + data: ArbitrumL2TxnBatchDAAnytrust; }; -const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { +const ArbitrumL2TxnBatchDetailsAnyTrustDA = ({ data }: Props) => { const signersBg = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); return ( @@ -25,26 +25,26 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { > Signature - { dataAvailability.bls_signature } + { data.bls_signature } Data hash - { dataAvailability.data_hash } - + { data.data_hash } + Timeout - { dayjs(dataAvailability.timeout) < dayjs() ? - : + { dayjs(data.timeout) < dayjs() ? + : ( <> - { dayjs(dataAvailability.timeout).format('llll') } + { dayjs(data.timeout).format('llll') } - { dayjs(dataAvailability.timeout).diff(dayjs(), 'day') } days left + { dayjs(data.timeout).diff(dayjs(), 'day') } days left ) } @@ -66,7 +66,7 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { Key Trusted Proof - { dataAvailability.signers.map(signer => ( + { data.signers.map(signer => ( <> { signer.key } @@ -88,7 +88,7 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { - { dataAvailability.signers.map(signer => ( + { data.signers.map(signer => ( Key @@ -119,4 +119,4 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { ); }; -export default ArbitrumL2TxnBatchDetailsDA; +export default ArbitrumL2TxnBatchDetailsAnyTrustDA; diff --git a/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsCelestiaDA.tsx b/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsCelestiaDA.tsx new file mode 100644 index 0000000000..5caf6aa16c --- /dev/null +++ b/ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetailsCelestiaDA.tsx @@ -0,0 +1,52 @@ +import { Flex } from '@chakra-ui/react'; +import React from 'react'; + +import type { ArbitrumL2TxnBatchDACelestia } from 'types/api/arbitrumL2'; + +import config from 'configs/app'; +import CeleniumLink from 'ui/shared/batch/CeleniumLink'; +import CopyToClipboard from 'ui/shared/CopyToClipboard'; +import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; + +const feature = config.features.rollup; + +interface Props { + data: ArbitrumL2TxnBatchDACelestia; +} + +const ArbitrumL2TxnBatchDetailsCelestiaDA = ({ data }: Props) => { + return ( + <> + + Height + + + { data.height } + + + + Commitment + + + + + + + { feature.isEnabled && feature.DA.celestia.namespace && ( + + ) } + + + ); +}; + +export default ArbitrumL2TxnBatchDetailsCelestiaDA; diff --git a/ui/txnBatches/arbitrumL2/useBatchQuery.tsx b/ui/txnBatches/arbitrumL2/useBatchQuery.tsx new file mode 100644 index 0000000000..0e66b2a8b3 --- /dev/null +++ b/ui/txnBatches/arbitrumL2/useBatchQuery.tsx @@ -0,0 +1,30 @@ +import { useRouter } from 'next/router'; + +import useApiQuery from 'lib/api/useApiQuery'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import { ARBITRUM_L2_TXN_BATCH } from 'stubs/arbitrumL2'; + +export default function useBatchQuery() { + const router = useRouter(); + const number = getQueryParamString(router.query.number); + const height = getQueryParamString(router.query.height); + const commitment = getQueryParamString(router.query.commitment); + + const batchByNumberQuery = useApiQuery('arbitrum_l2_txn_batch', { + pathParams: { number }, + queryOptions: { + enabled: Boolean(number), + placeholderData: ARBITRUM_L2_TXN_BATCH, + }, + }); + + const batchByHeightQuery = useApiQuery('arbitrum_l2_txn_batch_celestia', { + pathParams: { height, commitment }, + queryOptions: { + enabled: Boolean(height && commitment), + placeholderData: ARBITRUM_L2_TXN_BATCH, + }, + }); + + return number ? batchByNumberQuery : batchByHeightQuery; +} diff --git a/ui/txnBatches/optimisticL2/OptimisticL2TxnBatchBlobCelestia.tsx b/ui/txnBatches/optimisticL2/OptimisticL2TxnBatchBlobCelestia.tsx index 5eb46361bf..9f2f18d474 100644 --- a/ui/txnBatches/optimisticL2/OptimisticL2TxnBatchBlobCelestia.tsx +++ b/ui/txnBatches/optimisticL2/OptimisticL2TxnBatchBlobCelestia.tsx @@ -1,30 +1,16 @@ -import { Flex, GridItem, Icon, VStack } from '@chakra-ui/react'; +import { Flex, GridItem, VStack } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2BlobTypeCelestia } from 'types/api/optimisticL2'; -// This icon doesn't work properly when it is in the sprite -// Probably because of the gradient -// eslint-disable-next-line no-restricted-imports -import celeniumIcon from 'icons/brands/celenium.svg'; import dayjs from 'lib/date/dayjs'; -import hexToBase64 from 'lib/hexToBase64'; +import CeleniumLink from 'ui/shared/batch/CeleniumLink'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; -import LinkExternal from 'ui/shared/links/LinkExternal'; import OptimisticL2TxnBatchBlobWrapper from './OptimisticL2TxnBatchBlobWrapper'; -function getCeleniumUrl(blob: OptimisticL2BlobTypeCelestia) { - const url = new URL('https://mocha.celenium.io/blob'); - url.searchParams.set('commitment', hexToBase64(blob.commitment)); - url.searchParams.set('hash', hexToBase64(blob.namespace)); - url.searchParams.set('height', String(blob.height)); - - return url.toString(); -} - interface Props { blobs: Array; isLoading: boolean; @@ -43,10 +29,7 @@ const OptimisticL2TxnBatchBlobCelestia = ({ blobs, isLoading }: Props) => { - - - Blob page - + Height { blob.height } diff --git a/ui/txnBatches/optimisticL2/useBatchQuery.tsx b/ui/txnBatches/optimisticL2/useBatchQuery.tsx new file mode 100644 index 0000000000..accb2b05ee --- /dev/null +++ b/ui/txnBatches/optimisticL2/useBatchQuery.tsx @@ -0,0 +1,30 @@ +import { useRouter } from 'next/router'; + +import useApiQuery from 'lib/api/useApiQuery'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import { L2_TXN_BATCH } from 'stubs/L2'; + +export default function useBatchQuery() { + const router = useRouter(); + const number = getQueryParamString(router.query.number); + const height = getQueryParamString(router.query.height); + const commitment = getQueryParamString(router.query.commitment); + + const batchByNumberQuery = useApiQuery('optimistic_l2_txn_batch', { + pathParams: { number }, + queryOptions: { + enabled: Boolean(number), + placeholderData: L2_TXN_BATCH, + }, + }); + + const batchByHeightQuery = useApiQuery('optimistic_l2_txn_batch_celestia', { + pathParams: { height, commitment }, + queryOptions: { + enabled: Boolean(height && commitment), + placeholderData: L2_TXN_BATCH, + }, + }); + + return number ? batchByNumberQuery : batchByHeightQuery; +}