Commit 2747e02d authored by tom goriunov's avatar tom goriunov Committed by GitHub

Show Celestia Blobs on Arbitrum chains (#2562)

* DA info

* ENV variable

* celestia batch page

* tests

* fix ts

* [skip ci] fixes
parent 7a6ea8c1
...@@ -26,6 +26,7 @@ on: ...@@ -26,6 +26,7 @@ on:
- optimism_celestia - optimism_celestia
- optimism_sepolia - optimism_sepolia
- polygon - polygon
- rari_testnet
- rootstock - rootstock
- shibarium - shibarium
- scroll_sepolia - scroll_sepolia
......
...@@ -374,6 +374,7 @@ ...@@ -374,6 +374,7 @@
"optimism_celestia", "optimism_celestia",
"optimism_sepolia", "optimism_sepolia",
"polygon", "polygon",
"rari_testnet",
"rootstock_testnet", "rootstock_testnet",
"shibarium", "shibarium",
"stability_testnet", "stability_testnet",
......
...@@ -23,6 +23,11 @@ const config: Feature<{ ...@@ -23,6 +23,11 @@ const config: Feature<{
outputRootsEnabled: boolean; outputRootsEnabled: boolean;
L2WithdrawalUrl: string | undefined; L2WithdrawalUrl: string | undefined;
parentChainName: string | undefined; parentChainName: string | undefined;
DA: {
celestia: {
namespace: string | undefined;
};
};
}> = (() => { }> = (() => {
if (type && L1BaseUrl) { if (type && L1BaseUrl) {
return Object.freeze({ return Object.freeze({
...@@ -36,6 +41,11 @@ const config: Feature<{ ...@@ -36,6 +41,11 @@ const config: Feature<{
homepage: { homepage: {
showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true',
}, },
DA: {
celestia: {
namespace: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE') : undefined,
},
},
}); });
} }
......
# 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
...@@ -321,6 +321,16 @@ const rollupSchema = yup ...@@ -321,6 +321,16 @@ const rollupSchema = yup
value => value === undefined, 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 const celoSchema = yup
......
...@@ -2,3 +2,4 @@ NEXT_PUBLIC_ROLLUP_TYPE=arbitrum ...@@ -2,3 +2,4 @@ NEXT_PUBLIC_ROLLUP_TYPE=arbitrum
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com
NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true
NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME=DuckChain NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME=DuckChain
NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE=0x00000000000000000000000000000000000000ca1de12a9905be97beaf
\ No newline at end of file
...@@ -457,6 +457,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi ...@@ -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_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_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 <chain-name>"). This setting is applicable only for Arbitrum-based chains. | - | - | `DuckChain` | v1.37.0+ | | NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME | `string` | Set to customize L1 transaction status labels in the UI (e.g., "Sent to <chain-name>"). 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+ |
&nbsp; &nbsp;
......
...@@ -819,6 +819,11 @@ export const RESOURCES = { ...@@ -819,6 +819,11 @@ export const RESOURCES = {
pathParams: [ 'number' as const ], 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: { optimistic_l2_txn_batch_txs: {
path: '/api/v2/transactions/optimism-batch/:number', path: '/api/v2/transactions/optimism-batch/:number',
pathParams: [ 'number' as const ], pathParams: [ 'number' as const ],
...@@ -904,6 +909,11 @@ export const RESOURCES = { ...@@ -904,6 +909,11 @@ export const RESOURCES = {
pathParams: [ 'number' as const ], 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: { arbitrum_l2_txn_batch_txs: {
path: '/api/v2/transactions/arbitrum-batch/:number', path: '/api/v2/transactions/arbitrum-batch/:number',
pathParams: [ 'number' as const ], pathParams: [ 'number' as const ],
...@@ -1339,6 +1349,7 @@ Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : ...@@ -1339,6 +1349,7 @@ Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse :
Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse : Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse :
Q extends 'optimistic_l2_txn_batches_count' ? number : Q extends 'optimistic_l2_txn_batches_count' ? number :
Q extends 'optimistic_l2_txn_batch' ? OptimismL2TxnBatch : 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_txs' ? OptimismL2BatchTxs :
Q extends 'optimistic_l2_txn_batch_blocks' ? OptimismL2BatchBlocks : Q extends 'optimistic_l2_txn_batch_blocks' ? OptimismL2BatchBlocks :
Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse : Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse :
...@@ -1373,6 +1384,7 @@ Q extends 'arbitrum_l2_messages_count' ? number : ...@@ -1373,6 +1384,7 @@ Q extends 'arbitrum_l2_messages_count' ? number :
Q extends 'arbitrum_l2_txn_batches' ? ArbitrumL2TxnBatchesResponse : Q extends 'arbitrum_l2_txn_batches' ? ArbitrumL2TxnBatchesResponse :
Q extends 'arbitrum_l2_txn_batches_count' ? number : Q extends 'arbitrum_l2_txn_batches_count' ? number :
Q extends 'arbitrum_l2_txn_batch' ? ArbitrumL2TxnBatch : 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_txs' ? ArbitrumL2BatchTxs :
Q extends 'arbitrum_l2_txn_batch_blocks' ? ArbitrumL2BatchBlocks : Q extends 'arbitrum_l2_txn_batch_blocks' ? ArbitrumL2BatchBlocks :
Q extends 'zkevm_l2_deposits' ? ZkEvmL2DepositsResponse : Q extends 'zkevm_l2_deposits' ? ZkEvmL2DepositsResponse :
......
...@@ -43,6 +43,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -43,6 +43,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/dispute-games': 'Root page', '/dispute-games': 'Root page',
'/batches': 'Root page', '/batches': 'Root page',
'/batches/[number]': 'Regular page', '/batches/[number]': 'Regular page',
'/batches/celestia/[height]/[commitment]': 'Regular page',
'/blobs/[hash]': 'Regular page', '/blobs/[hash]': 'Regular page',
'/ops': 'Root page', '/ops': 'Root page',
'/op/[hash]': 'Regular page', '/op/[hash]': 'Regular page',
......
...@@ -46,6 +46,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -46,6 +46,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/dispute-games': DEFAULT_TEMPLATE, '/dispute-games': DEFAULT_TEMPLATE,
'/batches': DEFAULT_TEMPLATE, '/batches': DEFAULT_TEMPLATE,
'/batches/[number]': DEFAULT_TEMPLATE, '/batches/[number]': DEFAULT_TEMPLATE,
'/batches/celestia/[height]/[commitment]': DEFAULT_TEMPLATE,
'/blobs/[hash]': DEFAULT_TEMPLATE, '/blobs/[hash]': DEFAULT_TEMPLATE,
'/ops': DEFAULT_TEMPLATE, '/ops': DEFAULT_TEMPLATE,
'/op/[hash]': DEFAULT_TEMPLATE, '/op/[hash]': DEFAULT_TEMPLATE,
......
...@@ -43,6 +43,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -43,6 +43,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/dispute-games': '%network_name% dispute games', '/dispute-games': '%network_name% dispute games',
'/batches': '%network_name% txn batches', '/batches': '%network_name% txn batches',
'/batches/[number]': '%network_name% L2 txn batch %number%', '/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', '/blobs/[hash]': '%network_name% blob %hash% details',
'/ops': 'User operations on %network_name% - %network_name% explorer', '/ops': 'User operations on %network_name% - %network_name% explorer',
'/op/[hash]': '%network_name% user operation %hash%', '/op/[hash]': '%network_name% user operation %hash%',
......
...@@ -41,6 +41,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -41,6 +41,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/dispute-games': 'Dispute games', '/dispute-games': 'Dispute games',
'/batches': 'Txn batches', '/batches': 'Txn batches',
'/batches/[number]': 'L2 txn batch details', '/batches/[number]': 'L2 txn batch details',
'/batches/celestia/[height]/[commitment]': 'L2 txn batch details',
'/blobs/[hash]': 'Blob details', '/blobs/[hash]': 'Blob details',
'/ops': 'User operations', '/ops': 'User operations',
'/op/[hash]': 'User operation details', '/op/[hash]': 'User operation details',
......
...@@ -3,6 +3,7 @@ export const URL_PREFIX = /^https?:\/\//i; ...@@ -3,6 +3,7 @@ export const URL_PREFIX = /^https?:\/\//i;
export const IPFS_PREFIX = /^ipfs:\/\//i; export const IPFS_PREFIX = /^ipfs:\/\//i;
export const HEX_REGEXP = /^(?:0x)?[\da-fA-F]+$/; 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; export const FILE_EXTENSION = /\.([\da-z]+)$/i;
......
...@@ -38,3 +38,16 @@ export const batchDataAnytrust: ArbitrumL2TxnBatch = { ...@@ -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',
},
};
...@@ -123,6 +123,16 @@ export const batch: GetServerSideProps<Props> = async(context) => { ...@@ -123,6 +123,16 @@ export const batch: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const batchCelestia: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'arbitrum' || rollupFeature.type === 'optimistic'))) {
return {
notFound: true,
};
}
return base(context);
};
export const marketplace = async <Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext): export const marketplace = async <Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext):
Promise<GetServerSidePropsResult<Props<Pathname>>> => { Promise<GetServerSidePropsResult<Props<Pathname>>> => {
if (!config.features.marketplace.isEnabled) { if (!config.features.marketplace.isEnabled) {
......
...@@ -32,6 +32,7 @@ declare module "nextjs-routes" { ...@@ -32,6 +32,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/apps"> | StaticRoute<"/apps">
| StaticRoute<"/auth/profile"> | StaticRoute<"/auth/profile">
| DynamicRoute<"/batches/[number]", { "number": string }> | DynamicRoute<"/batches/[number]", { "number": string }>
| DynamicRoute<"/batches/celestia/[height]/[commitment]", { "height": string; "commitment": string }>
| StaticRoute<"/batches"> | StaticRoute<"/batches">
| DynamicRoute<"/blobs/[hash]", { "hash": string }> | DynamicRoute<"/blobs/[hash]", { "hash": string }>
| DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }>
......
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: Props) => {
return (
<PageNextJs pathname="/batches/celestia/[height]/[commitment]" query={ props.query }>
<Batch/>
</PageNextJs>
);
};
export default Page;
export { batchCelestia as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -26,6 +26,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = { ...@@ -26,6 +26,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'arbitrum' ], [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'arbitrum' ],
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ], [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
[ 'NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME', 'DuckChain' ], [ 'NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME', 'DuckChain' ],
[ 'NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE', '0x1234' ],
], ],
shibariumRollup: [ shibariumRollup: [
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ], [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ],
......
...@@ -19,6 +19,7 @@ const PRESETS = { ...@@ -19,6 +19,7 @@ const PRESETS = {
optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com', optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com',
optimism_sepolia: 'https://optimism-sepolia.blockscout.com', optimism_sepolia: 'https://optimism-sepolia.blockscout.com',
polygon: 'https://polygon.blockscout.com', polygon: 'https://polygon.blockscout.com',
rari_testnet: 'https://rari-testnet.cloud.blockscout.com',
rootstock_testnet: 'https://rootstock-testnet.blockscout.com', rootstock_testnet: 'https://rootstock-testnet.blockscout.com',
scroll_sepolia: 'https://scroll-sepolia.blockscout.com', scroll_sepolia: 'https://scroll-sepolia.blockscout.com',
shibarium: 'https://www.shibariumscan.io', shibarium: 'https://www.shibariumscan.io',
......
...@@ -74,8 +74,14 @@ export type ArbitrumL2TxnBatchDAAnytrust = { ...@@ -74,8 +74,14 @@ export type ArbitrumL2TxnBatchDAAnytrust = {
}>; }>;
}; };
export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | { export type ArbitrumL2TxnBatchDACelestia = {
batch_data_container: Exclude<BatchDataContainer, 'in_anytrust'>; batch_data_container: 'in_celestia';
height: number;
transaction_commitment: string;
};
export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | ArbitrumL2TxnBatchDACelestia | {
batch_data_container: Exclude<BatchDataContainer, 'in_anytrust' | 'in_celestia'>;
}; };
export type ArbitrumL2TxnBatch = { export type ArbitrumL2TxnBatch = {
......
import React from 'react'; 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 { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect, devices } from 'playwright/lib'; import { test, expect, devices } from 'playwright/lib';
...@@ -31,6 +31,13 @@ test('with anytrust DA', async({ render, mockApiResponse }) => { ...@@ -31,6 +31,13 @@ test('with anytrust DA', async({ render, mockApiResponse }) => {
await expect(component).toHaveScreenshot(); 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(<ArbitrumL2TxnBatch/>, { hooksConfig });
await component.getByText('Show data availability info').click();
await expect(component).toHaveScreenshot();
});
test.describe('mobile', () => { test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport }); test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ render, mockApiResponse }) => { test('base view', async({ render, mockApiResponse }) => {
...@@ -45,4 +52,12 @@ test.describe('mobile', () => { ...@@ -45,4 +52,12 @@ test.describe('mobile', () => {
await component.getByText('Show data availability info').click(); await component.getByText('Show data availability info').click();
await expect(component).toHaveScreenshot(); 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(<ArbitrumL2TxnBatch/>, { hooksConfig });
await component.getByText('Show data availability info').click();
await page.mouse.move(0, 0);
await expect(component).toHaveScreenshot();
});
}); });
...@@ -3,13 +3,11 @@ import React from 'react'; ...@@ -3,13 +3,11 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/Tabs/types'; import type { RoutedTab } from 'ui/shared/Tabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError'; import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { ARBITRUM_L2_TXN_BATCH } from 'stubs/arbitrumL2';
import { BLOCK } from 'stubs/block'; import { BLOCK } from 'stubs/block';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
...@@ -21,6 +19,7 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; ...@@ -21,6 +19,7 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import ArbitrumL2TxnBatchDetails from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails'; import ArbitrumL2TxnBatchDetails from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails';
import useBatchQuery from 'ui/txnBatches/arbitrumL2/useBatchQuery';
import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
const TAB_LIST_PROPS = { const TAB_LIST_PROPS = {
...@@ -35,20 +34,16 @@ const ArbitrumL2TxnBatch = () => { ...@@ -35,20 +34,16 @@ const ArbitrumL2TxnBatch = () => {
const router = useRouter(); const router = useRouter();
const appProps = useAppContext(); const appProps = useAppContext();
const number = getQueryParamString(router.query.number); 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 tab = getQueryParamString(router.query.tab);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const batchQuery = useApiQuery('arbitrum_l2_txn_batch', { const batchQuery = useBatchQuery();
pathParams: { number },
queryOptions: {
enabled: Boolean(number),
placeholderData: ARBITRUM_L2_TXN_BATCH,
},
});
const batchTxsQuery = useQueryWithPages({ const batchTxsQuery = useQueryWithPages({
resourceName: 'arbitrum_l2_txn_batch_txs', resourceName: 'arbitrum_l2_txn_batch_txs',
pathParams: { number }, pathParams: { number: String(batchQuery.data?.number) },
options: { options: {
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'),
placeholderData: generateListStub<'arbitrum_l2_txn_batch_txs'>(TX, 50, { next_page_params: { placeholderData: generateListStub<'arbitrum_l2_txn_batch_txs'>(TX, 50, { next_page_params: {
...@@ -62,7 +57,7 @@ const ArbitrumL2TxnBatch = () => { ...@@ -62,7 +57,7 @@ const ArbitrumL2TxnBatch = () => {
const batchBlocksQuery = useQueryWithPages({ const batchBlocksQuery = useQueryWithPages({
resourceName: 'arbitrum_l2_txn_batch_blocks', resourceName: 'arbitrum_l2_txn_batch_blocks',
pathParams: { number }, pathParams: { number: String(batchQuery.data?.number) },
options: { options: {
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'), enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'),
placeholderData: generateListStub<'arbitrum_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { placeholderData: generateListStub<'arbitrum_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: {
...@@ -73,7 +68,7 @@ const ArbitrumL2TxnBatch = () => { ...@@ -73,7 +68,7 @@ const ArbitrumL2TxnBatch = () => {
}, },
}); });
throwOnAbsentParamError(number); throwOnAbsentParamError(number || (height && commitment));
throwOnResourceLoadError(batchQuery); throwOnResourceLoadError(batchQuery);
let pagination; let pagination;
...@@ -117,8 +112,9 @@ const ArbitrumL2TxnBatch = () => { ...@@ -117,8 +112,9 @@ const ArbitrumL2TxnBatch = () => {
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
title={ `Txn batch #${ number }` } title={ `Txn batch #${ batchQuery.data?.number }` }
backLink={ backLink } backLink={ backLink }
isLoading={ batchQuery.isPlaceholderData }
/> />
{ batchQuery.isPlaceholderData ? { batchQuery.isPlaceholderData ?
<TabsSkeleton tabs={ tabs }/> : ( <TabsSkeleton tabs={ tabs }/> : (
......
...@@ -2,11 +2,11 @@ import React from 'react'; ...@@ -2,11 +2,11 @@ import React from 'react';
import * as arbitrumTxnBatchesMock from 'mocks/arbitrum/txnBatches'; import * as arbitrumTxnBatchesMock from 'mocks/arbitrum/txnBatches';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib'; import { test, expect, devices } from 'playwright/lib';
import ArbitrumL2TxnBatches from './ArbitrumL2TxnBatches'; import ArbitrumL2TxnBatches from './ArbitrumL2TxnBatches';
test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
test.slow(); test.slow();
await mockEnvs(ENVS_MAP.arbitrumRollup); await mockEnvs(ENVS_MAP.arbitrumRollup);
await mockTextAd(); await mockTextAd();
...@@ -16,3 +16,17 @@ test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse ...@@ -16,3 +16,17 @@ test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse
const component = await render(<ArbitrumL2TxnBatches/>); const component = await render(<ArbitrumL2TxnBatches/>);
await expect(component).toHaveScreenshot(); 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(<ArbitrumL2TxnBatches/>);
await expect(component).toHaveScreenshot();
});
});
...@@ -3,14 +3,12 @@ import React from 'react'; ...@@ -3,14 +3,12 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/Tabs/types'; import type { RoutedTab } from 'ui/shared/Tabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError'; import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { BLOCK } from 'stubs/block'; import { BLOCK } from 'stubs/block';
import { L2_TXN_BATCH } from 'stubs/L2';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import BlocksContent from 'ui/blocks/BlocksContent'; import BlocksContent from 'ui/blocks/BlocksContent';
...@@ -21,6 +19,7 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; ...@@ -21,6 +19,7 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import OptimisticL2TxnBatchDetails from 'ui/txnBatches/optimisticL2/OptimisticL2TxnBatchDetails'; import OptimisticL2TxnBatchDetails from 'ui/txnBatches/optimisticL2/OptimisticL2TxnBatchDetails';
import useBatchQuery from 'ui/txnBatches/optimisticL2/useBatchQuery';
import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
const TAB_LIST_PROPS = { const TAB_LIST_PROPS = {
...@@ -35,20 +34,16 @@ const OptimisticL2TxnBatch = () => { ...@@ -35,20 +34,16 @@ const OptimisticL2TxnBatch = () => {
const router = useRouter(); const router = useRouter();
const appProps = useAppContext(); const appProps = useAppContext();
const number = getQueryParamString(router.query.number); 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 tab = getQueryParamString(router.query.tab);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const batchQuery = useApiQuery('optimistic_l2_txn_batch', { const batchQuery = useBatchQuery();
pathParams: { number },
queryOptions: {
enabled: Boolean(number),
placeholderData: L2_TXN_BATCH,
},
});
const batchTxsQuery = useQueryWithPages({ const batchTxsQuery = useQueryWithPages({
resourceName: 'optimistic_l2_txn_batch_txs', resourceName: 'optimistic_l2_txn_batch_txs',
pathParams: { number }, pathParams: { number: String(batchQuery.data?.internal_id) },
options: { options: {
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.internal_id && tab === 'txs'), enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.internal_id && tab === 'txs'),
placeholderData: generateListStub<'optimistic_l2_txn_batch_txs'>(TX, 50, { next_page_params: { placeholderData: generateListStub<'optimistic_l2_txn_batch_txs'>(TX, 50, { next_page_params: {
...@@ -61,7 +56,7 @@ const OptimisticL2TxnBatch = () => { ...@@ -61,7 +56,7 @@ const OptimisticL2TxnBatch = () => {
const batchBlocksQuery = useQueryWithPages({ const batchBlocksQuery = useQueryWithPages({
resourceName: 'optimistic_l2_txn_batch_blocks', resourceName: 'optimistic_l2_txn_batch_blocks',
pathParams: { number }, pathParams: { number: String(batchQuery.data?.internal_id) },
options: { options: {
enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.internal_id && tab === 'blocks'), enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.internal_id && tab === 'blocks'),
placeholderData: generateListStub<'optimistic_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { placeholderData: generateListStub<'optimistic_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: {
...@@ -71,7 +66,7 @@ const OptimisticL2TxnBatch = () => { ...@@ -71,7 +66,7 @@ const OptimisticL2TxnBatch = () => {
}, },
}); });
throwOnAbsentParamError(number); throwOnAbsentParamError(number || (height && commitment));
throwOnResourceLoadError(batchQuery); throwOnResourceLoadError(batchQuery);
let pagination; let pagination;
...@@ -115,8 +110,9 @@ const OptimisticL2TxnBatch = () => { ...@@ -115,8 +110,9 @@ const OptimisticL2TxnBatch = () => {
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
title={ `Batch #${ number }` } title={ `Batch #${ batchQuery.data?.internal_id }` }
backLink={ backLink } backLink={ backLink }
isLoading={ batchQuery.isPlaceholderData }
/> />
{ batchQuery.isPlaceholderData ? { batchQuery.isPlaceholderData ?
<TabsSkeleton tabs={ tabs }/> : ( <TabsSkeleton tabs={ tabs }/> : (
......
...@@ -19,16 +19,16 @@ const ArbitrumL2TxnBatchDA = ({ dataContainer, isLoading }: Props) => { ...@@ -19,16 +19,16 @@ const ArbitrumL2TxnBatchDA = ({ dataContainer, isLoading }: Props) => {
switch (dataContainer) { switch (dataContainer) {
case 'in_blob4844': case 'in_blob4844':
text = 'blob'; text = 'Blob';
break; break;
case 'in_anytrust': case 'in_anytrust':
text = 'anytrust'; text = 'AnyTrust';
break; break;
case 'in_calldata': case 'in_calldata':
text = 'calldata'; text = 'Calldata';
break; break;
case 'in_celestia': case 'in_celestia':
text = 'celestia'; text = 'Celestia';
break; break;
default: default:
text = ''; text = '';
......
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 (
<Flex alignItems="center" columnGap={ 2 }>
<Icon as={ celeniumIcon } boxSize={ 5 }/>
<LinkExternal href={ getCeleniumUrl(props) }>Blob page</LinkExternal>
</Flex>
);
};
export default React.memo(CeleniumLink);
...@@ -23,7 +23,8 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; ...@@ -23,7 +23,8 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import PrevNext from 'ui/shared/PrevNext'; import PrevNext from 'ui/shared/PrevNext';
import ArbitrumL2TxnBatchDetailsDA from './ArbitrumL2TxnBatchDetailsDA'; import ArbitrumL2TxnBatchDetailsAnyTrustDA from './ArbitrumL2TxnBatchDetailsAnyTrustDA';
import ArbitrumL2TxnBatchDetailsCelestiaDA from './ArbitrumL2TxnBatchDetailsCelestiaDA';
interface Props { interface Props {
query: UseQueryResult<ArbitrumL2TxnBatch, ResourceError>; query: UseQueryResult<ArbitrumL2TxnBatch, ResourceError>;
} }
...@@ -181,11 +182,11 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => { ...@@ -181,11 +182,11 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
> >
Before acc Before acc
</DetailsInfoItem.Label> </DetailsInfoItem.Label>
<DetailsInfoItem.Value> <DetailsInfoItem.Value flexWrap="nowrap" >
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden"> <Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.before_acc }/> <HashStringShortenDynamic hash={ data.before_acc }/>
<CopyToClipboard text={ data.before_acc }/>
</Skeleton> </Skeleton>
<CopyToClipboard text={ data.before_acc } isLoading={ isPlaceholderData }/>
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
<DetailsInfoItem.Label <DetailsInfoItem.Label
...@@ -194,14 +195,14 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => { ...@@ -194,14 +195,14 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
> >
After acc After acc
</DetailsInfoItem.Label> </DetailsInfoItem.Label>
<DetailsInfoItem.Value> <DetailsInfoItem.Value flexWrap="nowrap">
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden"> <Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.after_acc }/> <HashStringShortenDynamic hash={ data.after_acc }/>
<CopyToClipboard text={ data.after_acc }/>
</Skeleton> </Skeleton>
<CopyToClipboard text={ data.after_acc } isLoading={ isPlaceholderData }/>
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
{ 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 */ } { /* CUT */ }
<GridItem colSpan={{ base: undefined, lg: 2 }}> <GridItem colSpan={{ base: undefined, lg: 2 }}>
...@@ -224,7 +225,12 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => { ...@@ -224,7 +225,12 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
<> <>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/> <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<ArbitrumL2TxnBatchDetailsDA dataAvailability={ data.data_availability }/> { data.data_availability.batch_data_container === 'in_anytrust' && (
<ArbitrumL2TxnBatchDetailsAnyTrustDA data={ data.data_availability }/>
) }
{ data.data_availability.batch_data_container === 'in_celestia' && (
<ArbitrumL2TxnBatchDetailsCelestiaDA data={ data.data_availability }/>
) }
</> </>
) } ) }
</> </>
......
...@@ -12,10 +12,10 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -12,10 +12,10 @@ import IconSvg from 'ui/shared/IconSvg';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
type Props = { type Props = {
dataAvailability: ArbitrumL2TxnBatchDAAnytrust; data: ArbitrumL2TxnBatchDAAnytrust;
}; };
const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { const ArbitrumL2TxnBatchDetailsAnyTrustDA = ({ data }: Props) => {
const signersBg = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const signersBg = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
...@@ -25,26 +25,26 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { ...@@ -25,26 +25,26 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => {
> >
Signature Signature
</DetailsInfoItem.Label><DetailsInfoItem.Value wordBreak="break-all" whiteSpace="break-spaces"> </DetailsInfoItem.Label><DetailsInfoItem.Value wordBreak="break-all" whiteSpace="break-spaces">
{ dataAvailability.bls_signature } { data.bls_signature }
</DetailsInfoItem.Value><DetailsInfoItem.Label </DetailsInfoItem.Value><DetailsInfoItem.Label
hint="The hash of the data blob stored by the AnyTrust committee" hint="The hash of the data blob stored by the AnyTrust committee"
> >
Data hash Data hash
</DetailsInfoItem.Label><DetailsInfoItem.Value> </DetailsInfoItem.Label><DetailsInfoItem.Value>
{ dataAvailability.data_hash } { data.data_hash }
<CopyToClipboard text={ dataAvailability.data_hash } ml={ 2 }/> <CopyToClipboard text={ data.data_hash } ml={ 2 }/>
</DetailsInfoItem.Value><DetailsInfoItem.Label </DetailsInfoItem.Value><DetailsInfoItem.Label
hint="Expiration timeout for the data blob" hint="Expiration timeout for the data blob"
> >
Timeout Timeout
</DetailsInfoItem.Label><DetailsInfoItem.Value> </DetailsInfoItem.Label><DetailsInfoItem.Value>
{ dayjs(dataAvailability.timeout) < dayjs() ? { dayjs(data.timeout) < dayjs() ?
<DetailsTimestamp timestamp={ dataAvailability.timeout }/> : <DetailsTimestamp timestamp={ data.timeout }/> :
( (
<> <>
<Text>{ dayjs(dataAvailability.timeout).format('llll') }</Text> <Text>{ dayjs(data.timeout).format('llll') }</Text>
<TextSeparator color="gray.500"/> <TextSeparator color="gray.500"/>
<Text color="red.500">{ dayjs(dataAvailability.timeout).diff(dayjs(), 'day') } days left</Text> <Text color="red.500">{ dayjs(data.timeout).diff(dayjs(), 'day') } days left</Text>
</> </>
) } ) }
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
...@@ -66,7 +66,7 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { ...@@ -66,7 +66,7 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => {
<Text fontWeight={ 600 }>Key</Text> <Text fontWeight={ 600 }>Key</Text>
<Text fontWeight={ 600 }>Trusted</Text> <Text fontWeight={ 600 }>Trusted</Text>
<Text fontWeight={ 600 }>Proof</Text> <Text fontWeight={ 600 }>Proof</Text>
{ dataAvailability.signers.map(signer => ( { data.signers.map(signer => (
<> <>
<Flex justifyContent="space-between"> <Flex justifyContent="space-between">
<Text wordBreak="break-all" whiteSpace="break-spaces">{ signer.key }</Text> <Text wordBreak="break-all" whiteSpace="break-spaces">{ signer.key }</Text>
...@@ -88,7 +88,7 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { ...@@ -88,7 +88,7 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => {
<Hide above="lg" ssr={ false }> <Hide above="lg" ssr={ false }>
<Box backgroundColor={ signersBg } borderRadius="md"> <Box backgroundColor={ signersBg } borderRadius="md">
{ dataAvailability.signers.map(signer => ( { data.signers.map(signer => (
<VStack padding={ 4 } key={ signer.key } gap={ 2 }> <VStack padding={ 4 } key={ signer.key } gap={ 2 }>
<Flex w="100%" justifyContent="space-between"> <Flex w="100%" justifyContent="space-between">
<Text fontWeight={ 600 }>Key</Text> <Text fontWeight={ 600 }>Key</Text>
...@@ -119,4 +119,4 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => { ...@@ -119,4 +119,4 @@ const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => {
); );
}; };
export default ArbitrumL2TxnBatchDetailsDA; export default ArbitrumL2TxnBatchDetailsAnyTrustDA;
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 (
<>
<DetailsInfoItem.Label
hint="The block number in Celestia where the Data Availability blob was published"
>
Height
</DetailsInfoItem.Label>
<DetailsInfoItem.Value wordBreak="break-all" whiteSpace="break-spaces">
{ data.height }
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="The Data Availability blob’s unique cryptographic proof"
>
Commitment
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<Flex overflow="hidden" minW="0">
<HashStringShortenDynamic hash={ data.transaction_commitment }/>
</Flex>
<CopyToClipboard text={ data.transaction_commitment } mr={ 3 }/>
{ feature.isEnabled && feature.DA.celestia.namespace && (
<CeleniumLink
commitment={ data.transaction_commitment }
namespace={ feature.DA.celestia.namespace }
height={ data.height }
/>
) }
</DetailsInfoItem.Value>
</>
);
};
export default ArbitrumL2TxnBatchDetailsCelestiaDA;
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;
}
import { Flex, GridItem, Icon, VStack } from '@chakra-ui/react'; import { Flex, GridItem, VStack } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { OptimisticL2BlobTypeCelestia } from 'types/api/optimisticL2'; 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 dayjs from 'lib/date/dayjs';
import hexToBase64 from 'lib/hexToBase64'; import CeleniumLink from 'ui/shared/batch/CeleniumLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/links/LinkExternal';
import OptimisticL2TxnBatchBlobWrapper from './OptimisticL2TxnBatchBlobWrapper'; 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 { interface Props {
blobs: Array<OptimisticL2BlobTypeCelestia>; blobs: Array<OptimisticL2BlobTypeCelestia>;
isLoading: boolean; isLoading: boolean;
...@@ -43,10 +29,7 @@ const OptimisticL2TxnBatchBlobCelestia = ({ blobs, isLoading }: Props) => { ...@@ -43,10 +29,7 @@ const OptimisticL2TxnBatchBlobCelestia = ({ blobs, isLoading }: Props) => {
<CopyToClipboard text={ blob.commitment }/> <CopyToClipboard text={ blob.commitment }/>
</Flex> </Flex>
</GridItem> </GridItem>
<GridItem display="flex" columnGap={ 2 }> <CeleniumLink commitment={ blob.commitment } namespace={ blob.namespace } height={ blob.height }/>
<Icon as={ celeniumIcon } boxSize={ 5 }/>
<LinkExternal href={ getCeleniumUrl(blob) }>Blob page</LinkExternal>
</GridItem>
<GridItem fontWeight={ 600 }>Height</GridItem> <GridItem fontWeight={ 600 }>Height</GridItem>
<GridItem colSpan={ 2 }> <GridItem colSpan={ 2 }>
{ blob.height } { blob.height }
......
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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment