diff --git a/configs/app/features/rollup.ts b/configs/app/features/rollup.ts index edd784b2af..3728e60c18 100644 --- a/configs/app/features/rollup.ts +++ b/configs/app/features/rollup.ts @@ -16,15 +16,23 @@ const L2WithdrawalUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL'); const title = 'Rollup (L2) chain'; -const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: string; homepage: { showLatestBlocks: boolean } }> = (() => { - +const config: Feature<{ + type: RollupType; + L1BaseUrl: string; + homepage: { showLatestBlocks: boolean }; + outputRootsEnabled: boolean; + L2WithdrawalUrl: string | undefined; + parentChainName: string | undefined; +}> = (() => { if (type && L1BaseUrl) { return Object.freeze({ title, isEnabled: true, type, L1BaseUrl: stripTrailingSlash(L1BaseUrl), - L2WithdrawalUrl, + L2WithdrawalUrl: type === 'optimistic' ? L2WithdrawalUrl : undefined, + outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') !== 'false', + parentChainName: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME') : undefined, homepage: { showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', }, diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 42072be04b..ee4bb251d4 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -287,6 +287,28 @@ const rollupSchema = yup then: (schema) => schema.test(urlTest).required(), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '), }), + NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED: yup + .boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'optimistic', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED can only be used if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' ', + value => value === undefined, + ), + }), + NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME: yup + .string() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'arbitrum', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME can only be used if NEXT_PUBLIC_ROLLUP_TYPE is set to \'arbitrum\' ', + value => value === undefined, + ), + }), NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS: yup .boolean() .when('NEXT_PUBLIC_ROLLUP_TYPE', { diff --git a/deploy/tools/envs-validator/test/.env.arbitrum b/deploy/tools/envs-validator/test/.env.arbitrum new file mode 100644 index 0000000000..ee0ce91f00 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.arbitrum @@ -0,0 +1,4 @@ +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 diff --git a/deploy/tools/envs-validator/test/.env.rollup b/deploy/tools/envs-validator/test/.env.optimism similarity index 64% rename from deploy/tools/envs-validator/test/.env.rollup rename to deploy/tools/envs-validator/test/.env.optimism index 04100c6abe..4821e1ca62 100644 --- a/deploy/tools/envs-validator/test/.env.rollup +++ b/deploy/tools/envs-validator/test/.env.optimism @@ -2,4 +2,5 @@ NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com NEXT_PUBLIC_FAULT_PROOF_ENABLED=true -NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true \ No newline at end of file +NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true +NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED=false \ No newline at end of file diff --git a/docs/ENVS.md b/docs/ENVS.md index 4e7c0c8514..d7db4ab213 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -439,6 +439,8 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ | | NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.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) | - | `true` | `false` | 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+ |   diff --git a/lib/getArbitrumVerificationStepStatus.ts b/lib/getArbitrumVerificationStepStatus.ts deleted file mode 100644 index 602da12152..0000000000 --- a/lib/getArbitrumVerificationStepStatus.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ArbitrumBatchStatus, ArbitrumL2TxData } from 'types/api/arbitrumL2'; - -type Args = { - status: ArbitrumBatchStatus; - commitment_transaction: ArbitrumL2TxData; - confirmation_transaction: ArbitrumL2TxData; -}; - -export default function getArbitrumVerificationStepStatus({ - status, - commitment_transaction: commitTx, - confirmation_transaction: confirmTx, -}: Args) { - if (status === 'Sent to base') { - if (commitTx.status === 'unfinalized') { - return 'pending'; - } - } - if (status === 'Confirmed on base') { - if (confirmTx.status === 'unfinalized') { - return 'pending'; - } - } - return 'finalized'; -} diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index b3bf63f39f..a064d37e32 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -120,7 +120,7 @@ export default function useNavItems(): ReturnType { blocks, rollupTxnBatches, rollupDisputeGames, - rollupFeature.type === 'optimistic' ? rollupOutputRoots : undefined, + rollupFeature.outputRootsEnabled ? rollupOutputRoots : undefined, ].filter(Boolean), [ userOps, diff --git a/lib/rollups/arbitrum.ts b/lib/rollups/arbitrum.ts new file mode 100644 index 0000000000..7dbaac58d3 --- /dev/null +++ b/lib/rollups/arbitrum.ts @@ -0,0 +1,41 @@ +import { ARBITRUM_L2_TX_BATCH_STATUSES, type ArbitrumBatchStatus, type ArbitrumL2TxData } from 'types/api/arbitrumL2'; + +import config from 'configs/app'; + +const rollupFeature = config.features.rollup; + +type Args = { + status: ArbitrumBatchStatus; + commitment_transaction: ArbitrumL2TxData; + confirmation_transaction: ArbitrumL2TxData; +}; + +export const VERIFICATION_STEPS_MAP: Record = { + 'Processed on rollup': 'Processed on rollup', + 'Sent to base': rollupFeature.isEnabled && rollupFeature.parentChainName ? `Sent to ${ rollupFeature.parentChainName }` : 'Sent to parent chain', + 'Confirmed on base': rollupFeature.isEnabled && rollupFeature.parentChainName ? + `Confirmed on ${ rollupFeature.parentChainName }` : + 'Confirmed on parent chain', +}; + +export const verificationSteps = (() => { + return ARBITRUM_L2_TX_BATCH_STATUSES.map((status) => VERIFICATION_STEPS_MAP[status]); +})(); + +export function getVerificationStepStatus({ + status, + commitment_transaction: commitTx, + confirmation_transaction: confirmTx, +}: Args) { + if (status === 'Sent to base') { + if (commitTx.status === 'unfinalized') { + return 'pending'; + } + } + if (status === 'Confirmed on base') { + if (confirmTx.status === 'unfinalized') { + return 'pending'; + } + } + return 'finalized'; +} diff --git a/mocks/txs/tx.ts b/mocks/txs/tx.ts index bae57c630e..f8a302eced 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -340,6 +340,33 @@ export const celoTxn: Transaction = { }, }; +export const arbitrumTxn: Transaction = { + ...base, + arbitrum: { + batch_number: 743991, + commitment_transaction: { + hash: '0x71a25e01dde129a308704de217d200ea42e0f5b8c221c8ba8b2b680ff347f708', + status: 'unfinalized', + timestamp: '2024-11-19T14:26:23.000000Z', + }, + confirmation_transaction: { + hash: null, + status: null, + timestamp: null, + }, + contains_message: null, + gas_used_for_l1: '129773', + gas_used_for_l2: '128313', + message_related_info: { + associated_l1_transaction: null, + message_status: 'Relayed', + }, + network_fee: '1283130000000', + poster_fee: '1297730000000', + status: 'Sent to base', + }, +}; + export const base2 = { ...base, hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index ecf6277d44..8009d6fc15 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -102,8 +102,8 @@ export const rollup: GetServerSideProps = async(context) => { return base(context); }; -export const optimisticRollup: GetServerSideProps = async(context) => { - if (!(rollupFeature.isEnabled && rollupFeature.type === 'optimistic')) { +export const outputRoots: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && rollupFeature.outputRootsEnabled)) { return { notFound: true, }; diff --git a/pages/output-roots/index.tsx b/pages/output-roots/index.tsx index 4ce87bec5a..c0fd3a841d 100644 --- a/pages/output-roots/index.tsx +++ b/pages/output-roots/index.tsx @@ -16,4 +16,4 @@ const Page: NextPage = () => { export default Page; -export { optimisticRollup as getServerSideProps } from 'nextjs/getServerSideProps'; +export { outputRoots as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright/fixtures/mockEnvs.ts b/playwright/fixtures/mockEnvs.ts index 689091e225..b5c48c2c3d 100644 --- a/playwright/fixtures/mockEnvs.ts +++ b/playwright/fixtures/mockEnvs.ts @@ -25,6 +25,7 @@ export const ENVS_MAP: Record> = { arbitrumRollup: [ [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'arbitrum' ], [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ], + [ 'NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME', 'DuckChain' ], ], shibariumRollup: [ [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ], diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index 03d5f4347f..7fcc714183 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -5,7 +5,6 @@ import { useRouter } from 'next/router'; import React from 'react'; import { scroller, Element } from 'react-scroll'; -import { ARBITRUM_L2_TX_BATCH_STATUSES } from 'types/api/arbitrumL2'; import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; import { route } from 'nextjs-routes'; @@ -13,10 +12,10 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import getBlockReward from 'lib/block/getBlockReward'; import { GWEI, WEI, WEI_IN_GWEI, ZERO } from 'lib/consts'; -import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus'; import { space } from 'lib/html-entities'; import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; +import * as arbitrum from 'lib/rollups/arbitrum'; import getQueryParamString from 'lib/router/getQueryParamString'; import { currencyUnits } from 'lib/units'; import OptimisticL2TxnBatchDA from 'ui/shared/batch/OptimisticL2TxnBatchDA'; @@ -317,9 +316,9 @@ const BlockDetails = ({ query }: Props) => { } { rollupFeature.type === 'arbitrum' && data.arbitrum && ( ) } diff --git a/ui/tx/details/TxInfo.pw.tsx b/ui/tx/details/TxInfo.pw.tsx index 4a34314d1c..e9f8c56497 100644 --- a/ui/tx/details/TxInfo.pw.tsx +++ b/ui/tx/details/TxInfo.pw.tsx @@ -117,3 +117,12 @@ test('stability customization', async({ render, page, mockEnvs }) => { maskColor: pwConfig.maskColor, }); }); + +test('arbitrum L1 status', async({ render, mockEnvs }) => { + await mockEnvs(ENVS_MAP.arbitrumRollup); + const component = await render(); + + const statusElement = component.locator('div').filter({ hasText: 'Processed on rollup' }).nth(2); + + await expect(statusElement).toHaveScreenshot(); +}); diff --git a/ui/tx/details/TxInfo.tsx b/ui/tx/details/TxInfo.tsx index 18ee2373a6..1c977eb500 100644 --- a/ui/tx/details/TxInfo.tsx +++ b/ui/tx/details/TxInfo.tsx @@ -16,7 +16,6 @@ import BigNumber from 'bignumber.js'; import React from 'react'; import { scroller, Element } from 'react-scroll'; -import { ARBITRUM_L2_TX_BATCH_STATUSES } from 'types/api/arbitrumL2'; import type { Transaction } from 'types/api/transaction'; import { ZKEVM_L2_TX_STATUSES } from 'types/api/transaction'; import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; @@ -25,8 +24,8 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import { WEI, WEI_IN_GWEI } from 'lib/consts'; -import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; +import * as arbitrum from 'lib/rollups/arbitrum'; import { MESSAGE_DESCRIPTIONS } from 'lib/tx/arbitrumMessageStatusDescription'; import getConfirmationDuration from 'lib/tx/getConfirmationDuration'; import { currencyUnits } from 'lib/units'; @@ -239,9 +238,9 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_arbitrum-L1-status-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_arbitrum-L1-status-1.png new file mode 100644 index 0000000000..f9e2364bf1 Binary files /dev/null and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_arbitrum-L1-status-1.png differ