Commit c087a678 authored by Max Alekseenko's avatar Max Alekseenko

Merge branch 'marketplace-admin-api' into suggest-ideas-button

parents 9f012489 1ee81f5b
...@@ -115,11 +115,58 @@ jobs: ...@@ -115,11 +115,58 @@ jobs:
run: yarn --frozen-lockfile --ignore-optional run: yarn --frozen-lockfile --ignore-optional
- name: Run Jest - name: Run Jest
run: yarn test:jest run: yarn test:jest --onlyChanged=${{ github.event_name == 'pull_request' }} --passWithNoTests
pw_affected_tests:
name: Resolve affected Playwright tests
runs-on: ubuntu-latest
needs: [ code_quality, envs_validation ]
if: github.event_name == 'pull_request'
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
cache: 'yarn'
- name: Cache node_modules
uses: actions/cache@v4
id: cache-node-modules
with:
path: |
node_modules
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
- name: Install script dependencies
run: cd ./deploy/tools/affected-tests && yarn --frozen-lockfile
- name: Run script
run: yarn test:pw:detect-affected
- name: Upload result file
uses: actions/upload-artifact@v4
with:
name: playwright-affected-tests
path: ./playwright/affected-tests.txt
retention-days: 3
pw_tests: pw_tests:
name: 'Playwright tests / Project: ${{ matrix.project }}' name: 'Playwright tests / Project: ${{ matrix.project }}'
needs: [ code_quality, envs_validation ] needs: [ code_quality, envs_validation, pw_affected_tests ]
if: |
always() &&
needs.code_quality.result == 'success' &&
needs.envs_validation.result == 'success' &&
(needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped')
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.41.1-focal image: mcr.microsoft.com/playwright:v1.41.1-focal
...@@ -156,8 +203,15 @@ jobs: ...@@ -156,8 +203,15 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true' if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional run: yarn --frozen-lockfile --ignore-optional
- name: Download affected tests list
if: ${{ needs.pw_affected_tests.result == 'success' }}
uses: actions/download-artifact@v4
with:
name: playwright-affected-tests
path: ./playwright
- name: Run PlayWright - name: Run PlayWright
run: yarn test:pw:ci run: yarn test:pw:ci --affected=${{ github.event_name == 'pull_request' }} --pass-with-no-tests
env: env:
HOME: /root HOME: /root
PW_PROJECT: ${{ matrix.project }} PW_PROJECT: ${{ matrix.project }}
......
...@@ -51,5 +51,6 @@ yarn-error.log* ...@@ -51,5 +51,6 @@ yarn-error.log*
/playwright/.cache/ /playwright/.cache/
/playwright/.browser/ /playwright/.browser/
/playwright/envs.js /playwright/envs.js
/playwright/affected-tests.txt
**.dec** **.dec**
\ No newline at end of file
...@@ -155,6 +155,27 @@ ...@@ -155,6 +155,27 @@
"instanceLimit": 1 "instanceLimit": 1
} }
}, },
{
"type": "shell",
"command": "yarn test:pw:detect-affected",
"problemMatcher": [],
"label": "pw: detect affected",
"detail": "detect PW tests affected by changes in current branch",
"presentation": {
"reveal": "always",
"panel": "shared",
"focus": true,
"close": false,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiBlue",
"id": "diff"
},
"runOptions": {
"instanceLimit": 1
},
},
// JEST TESTS // JEST TESTS
{ {
...@@ -305,6 +326,7 @@ ...@@ -305,6 +326,7 @@
"options": [ "options": [
"", "",
"--update-snapshots", "--update-snapshots",
"--update-snapshots --affected",
"--ui", "--ui",
], ],
"default": "" "default": ""
......
import type { Feature } from './types'; import type { Feature } from './types';
import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; import { SUPPORTED_AD_TEXT_PROVIDERS } from 'types/client/adProviders';
import type { AdTextProviders } from 'types/client/adProviders'; import type { AdTextProviders } from 'types/client/adProviders';
import { getEnvValue } from '../utils'; import { getEnvValue } from '../utils';
const provider: AdTextProviders = (() => { const provider: AdTextProviders = (() => {
const envValue = getEnvValue('NEXT_PUBLIC_AD_TEXT_PROVIDER') as AdTextProviders; const envValue = getEnvValue('NEXT_PUBLIC_AD_TEXT_PROVIDER') as AdTextProviders;
return envValue && SUPPORTED_AD_BANNER_PROVIDERS.includes(envValue) ? envValue : 'coinzilla'; return envValue && SUPPORTED_AD_TEXT_PROVIDERS.includes(envValue) ? envValue : 'coinzilla';
})(); })();
const title = 'Text ads'; const title = 'Text ads';
......
...@@ -13,7 +13,7 @@ export { default as marketplace } from './marketplace'; ...@@ -13,7 +13,7 @@ export { default as marketplace } from './marketplace';
export { default as mixpanel } from './mixpanel'; export { default as mixpanel } from './mixpanel';
export { default as nameService } from './nameService'; export { default as nameService } from './nameService';
export { default as restApiDocs } from './restApiDocs'; export { default as restApiDocs } from './restApiDocs';
export { default as optimisticRollup } from './optimisticRollup'; export { default as rollup } from './rollup';
export { default as safe } from './safe'; export { default as safe } from './safe';
export { default as sentry } from './sentry'; export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml'; export { default as sol2uml } from './sol2uml';
...@@ -23,4 +23,3 @@ export { default as txInterpretation } from './txInterpretation'; ...@@ -23,4 +23,3 @@ export { default as txInterpretation } from './txInterpretation';
export { default as userOps } from './userOps'; export { default as userOps } from './userOps';
export { default as verifiedTokens } from './verifiedTokens'; export { default as verifiedTokens } from './verifiedTokens';
export { default as web3Wallet } from './web3Wallet'; export { default as web3Wallet } from './web3Wallet';
export { default as zkEvmRollup } from './zkEvmRollup';
import type { Feature } from './types'; import type { Feature } from './types';
import type { RollupType } from 'types/client/rollup';
import { ROLLUP_TYPES } from 'types/client/rollup';
import { getEnvValue } from '../utils'; import { getEnvValue } from '../utils';
const type = (() => {
const envValue = getEnvValue('NEXT_PUBLIC_ROLLUP_TYPE');
return ROLLUP_TYPES.find((type) => type === envValue);
})();
const L1BaseUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L1_BASE_URL');
const L2WithdrawalUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL');
const title = 'Rollup (L2) chain'; const title = 'Rollup (L2) chain';
const config: Feature<{ L1BaseUrl: string; withdrawalUrl: string }> = (() => { const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: string }> = (() => {
const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL');
const withdrawalUrl = getEnvValue('NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL');
if ( if (type && L1BaseUrl) {
getEnvValue('NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK') === 'true' &&
L1BaseUrl &&
withdrawalUrl
) {
return Object.freeze({ return Object.freeze({
title, title,
isEnabled: true, isEnabled: true,
type,
L1BaseUrl, L1BaseUrl,
withdrawalUrl, L2WithdrawalUrl,
}); });
} }
......
import type { Feature } from './types';
import { getEnvValue } from '../utils';
const title = 'ZkEVM rollup (L2) chain';
const config: Feature<{ L1BaseUrl: string; withdrawalUrl?: string }> = (() => {
const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL');
const isZkEvm = getEnvValue('NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK') === 'true';
if (isZkEvm && L1BaseUrl) {
return Object.freeze({
title,
isEnabled: true,
L1BaseUrl,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -43,6 +43,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com ...@@ -43,6 +43,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_AD_BANNER_PROVIDER=hype
#meta #meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true
...@@ -44,8 +44,6 @@ NEXT_PUBLIC_APP_INSTANCE=jest ...@@ -44,8 +44,6 @@ NEXT_PUBLIC_APP_INSTANCE=jest
NEXT_PUBLIC_APP_ENV=testing NEXT_PUBLIC_APP_ENV=testing
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=false
NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=false
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_AUTH_URL=http://localhost:3100
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
......
...@@ -47,6 +47,6 @@ NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout ...@@ -47,6 +47,6 @@ NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
# Set of ENVs for zkevm (dev only)
# https://eth.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME='OP Goerli'
NEXT_PUBLIC_NETWORK_SHORT_NAME='OP Goerli'
NEXT_PUBLIC_NETWORK_ID=420
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://goerli.optimism.io
# api configuration
NEXT_PUBLIC_API_HOST=optimism-goerli.blockscout.com
NEXT_PUBLIC_API_PORT=80
NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/polygon-mainnet.json
## footer
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
# app features
NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
# NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
# rollup
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_L1_BASE_URL=https://eth-goerli.blockscout.com/
...@@ -41,8 +41,6 @@ NEXT_PUBLIC_APP_ENV=testing ...@@ -41,8 +41,6 @@ NEXT_PUBLIC_APP_ENV=testing
NEXT_PUBLIC_APP_INSTANCE=pw NEXT_PUBLIC_APP_INSTANCE=pw
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=false
NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=false
NEXT_PUBLIC_AD_BANNER_PROVIDER=slise NEXT_PUBLIC_AD_BANNER_PROVIDER=slise
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_AUTH_URL=http://localhost:3100
......
...@@ -47,5 +47,5 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com ...@@ -47,5 +47,5 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
# rollup # rollup
NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=true NEXT_PUBLIC_ROLLUP_TYPE=zkEvm
NEXT_PUBLIC_L1_BASE_URL=https://polygon.blockscout.com NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://polygon.blockscout.com
/node_modules
\ No newline at end of file
/* eslint-disable no-console */
const { execSync } = require('child_process');
const dependencyTree = require('dependency-tree');
const fs = require('fs');
const path = require('path');
const ROOT_DIR = path.resolve(__dirname, '../../../');
const TARGET_FILE = path.resolve(ROOT_DIR, './playwright/affected-tests.txt');
const NON_EXISTENT_DEPS = [];
const DIRECTORIES_WITH_TESTS = [
path.resolve(ROOT_DIR, './ui'),
];
function getAllPwFilesInDirectory(directory) {
const files = fs.readdirSync(directory, { recursive: true });
return files
.filter((file) => file.endsWith('.pw.tsx'))
.map((file) => path.join(directory, file));
}
function getFileDeps(filename, changedNpmModules) {
return dependencyTree.toList({
filename,
directory: ROOT_DIR,
filter: (path) => {
if (path.indexOf('node_modules') === -1) {
return true;
}
if (changedNpmModules.some((module) => path.startsWith(module))) {
return true;
}
return false;
},
tsConfig: path.resolve(ROOT_DIR, './tsconfig.json'),
nonExistent: NON_EXISTENT_DEPS,
});
}
async function getChangedFiles() {
const command = process.env.CI ?
`git diff --name-only origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ ROOT_DIR }` :
`git diff --name-only main $(git branch --show-current) -- ${ ROOT_DIR }`;
console.log('Executing command: ', command);
const files = execSync(command)
.toString()
.trim()
.split('\n')
.filter(Boolean);
return files.map((file) => path.join(ROOT_DIR, file));
}
function checkChangesInChakraTheme(changedFiles) {
const themeDir = path.resolve(ROOT_DIR, './theme');
return changedFiles.some((file) => file.startsWith(themeDir));
}
function checkChangesInSvgSprite(changedFiles) {
const iconDir = path.resolve(ROOT_DIR, './icons');
const areIconsChanged = changedFiles.some((file) => file.startsWith(iconDir));
if (!areIconsChanged) {
return false;
}
const svgNamesFile = path.resolve(ROOT_DIR, './public/icons/name.d.ts');
const areSvgNamesChanged = changedFiles.some((file) => file === svgNamesFile);
if (!areSvgNamesChanged) {
// If only the icons have changed and not the names in the SVG file, we will need to run all tests.
// This is because we cannot correctly identify the test files that depend on these changes.
return true;
}
// If the icon names have changed, then there should be changes in the components that use them.
// Otherwise, typescript would complain about that.
return false;
}
function createTargetFile(content) {
fs.writeFileSync(TARGET_FILE, content);
}
function getPackageJsonUpdatedProps(packageJsonFile) {
const command = process.env.CI ?
`git diff --unified=0 origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ packageJsonFile }` :
`git diff --unified=0 main $(git branch --show-current) -- ${ packageJsonFile }`;
console.log('Executing command: ', command);
const changedLines = execSync(command)
.toString()
.trim()
.split('\n')
.filter(Boolean)
.filter((line) => line.startsWith('+ ') || line.startsWith('- '));
const changedProps = [ ...new Set(
changedLines
.map((line) => line.replaceAll(' ', '').replaceAll('+', '').replaceAll('-', ''))
.map((line) => line.split(':')[0].replaceAll('"', '')),
) ];
return changedProps;
}
function getUpdatedNpmModules(changedFiles) {
const packageJsonFile = path.resolve(ROOT_DIR, './package.json');
if (!changedFiles.includes(packageJsonFile)) {
return [];
}
try {
const packageJsonContent = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'));
const usedNpmModules = [
...Object.keys(packageJsonContent.dependencies || {}),
...Object.keys(packageJsonContent.devDependencies || {}),
];
const updatedProps = getPackageJsonUpdatedProps(packageJsonFile);
return updatedProps.filter((prop) => usedNpmModules.includes(prop));
} catch (error) {}
}
async function run() {
// NOTES:
// - The absence of TARGET_FILE implies that all tests should be run.
// - The empty TARGET_FILE implies that no tests should be run.
const start = Date.now();
fs.unlink(TARGET_FILE, () => {});
const changedFiles = await getChangedFiles();
if (!changedFiles.length) {
createTargetFile('');
console.log('No changed files found. Exiting...');
return;
}
console.log('Changed files in the branch: ', changedFiles);
if (checkChangesInChakraTheme(changedFiles)) {
console.log('Changes in Chakra theme detected. It is advisable to run all test suites. Exiting...');
return;
}
if (checkChangesInSvgSprite(changedFiles)) {
console.log('There are some changes in the SVG sprite that cannot be linked to a specific component. It is advisable to run all test suites. Exiting...');
return;
}
let changedNpmModules = getUpdatedNpmModules(changedFiles);
if (!changedNpmModules) {
console.log('Some error occurred while detecting changed NPM modules. It is advisable to run all test suites. Exiting...');
return;
}
console.log('Changed NPM modules in the branch: ', changedNpmModules);
changedNpmModules = [
...changedNpmModules,
...changedNpmModules.map((module) => `@types/${ module }`), // there are some deps that are resolved to .d.ts files
].map((module) => path.resolve(ROOT_DIR, `./node_modules/${ module }`));
const allTestFiles = DIRECTORIES_WITH_TESTS.reduce((acc, dir) => {
return acc.concat(getAllPwFilesInDirectory(dir));
}, []);
const isDepChanged = (dep) => changedFiles.includes(dep) || changedNpmModules.some((module) => dep.startsWith(module));
const testFilesToRun = allTestFiles
.map((file) => ({ file, deps: getFileDeps(file, changedNpmModules) }))
.filter(({ deps }) => deps.some(isDepChanged));
const testFileNamesToRun = testFilesToRun.map(({ file }) => path.relative(ROOT_DIR, file));
if (!testFileNamesToRun.length) {
createTargetFile('');
console.log('No tests to run. Exiting...');
return;
}
createTargetFile(testFileNamesToRun.join('\n'));
const end = Date.now();
const testFilesToRunWithFilteredDeps = testFilesToRun.map(({ file, deps }) => ({
file,
deps: deps.filter(isDepChanged),
}));
console.log('Total time: ', ((end - start) / 1_000).toLocaleString());
console.log('Total test to run: ', testFileNamesToRun.length);
console.log('Tests to run with changed deps: ', testFilesToRunWithFilteredDeps);
console.log('Non existent deps: ', NON_EXISTENT_DEPS);
}
run();
{
"name": "affected-tests",
"version": "1.0.0",
"main": "index.js",
"author": "Vasilii (tom) Goriunov <tom@ohhhh.me>",
"license": "MIT",
"dependencies": {
"dependency-tree": "10.0.9"
}
}
This diff is collapsed.
...@@ -15,6 +15,7 @@ import type { ContractCodeIde } from '../../../types/client/contract'; ...@@ -15,6 +15,7 @@ import type { ContractCodeIde } from '../../../types/client/contract';
import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; import type { MarketplaceAppOverview } from '../../../types/client/marketplace';
import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items';
import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items';
import { ROLLUP_TYPES } from '../../../types/client/rollup';
import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token';
import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation'; import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation';
import type { WalletType } from '../../../types/client/wallets'; import type { WalletType } from '../../../types/client/wallets';
...@@ -123,23 +124,20 @@ const beaconChainSchema = yup ...@@ -123,23 +124,20 @@ const beaconChainSchema = yup
const rollupSchema = yup const rollupSchema = yup
.object() .object()
.shape({ .shape({
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: yup.boolean(), NEXT_PUBLIC_ROLLUP_TYPE: yup.string().oneOf(ROLLUP_TYPES),
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: yup NEXT_PUBLIC_ROLLUP_L1_BASE_URL: yup
.string() .string()
.when('NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', { .when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: (value: string) => value, is: (value: string) => value,
then: (schema) => schema.test(urlTest).required(), then: (schema) => schema.test(urlTest).required(),
// eslint-disable-next-line max-len otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK is not set to "true"'),
}), }),
NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK: yup.boolean(), NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: yup
NEXT_PUBLIC_L1_BASE_URL: yup
.string() .string()
.when([ 'NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK' ], { .when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: (isOptimistic?: boolean, isZk?: boolean) => isOptimistic || isZk, is: (value: string) => value === 'optimistic',
then: (schema) => schema.test(urlTest).required(), then: (schema) => schema.test(urlTest).required(),
// eslint-disable-next-line max-len otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_L1_BASE_URL cannot not be used if NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK or NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK is not set to "true"'),
}), }),
}); });
......
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_L1_BASE_URL=https://example.com NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://example.com NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com
\ No newline at end of file \ No newline at end of file
...@@ -192,9 +192,9 @@ frontend: ...@@ -192,9 +192,9 @@ frontend:
NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-test.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-test.k8s-dev.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: "true" NEXT_PUBLIC_ROLLUP_TYPE: optimistic
NEXT_PUBLIC_L1_BASE_URL: https://eth-goerli.blockscout.com/ NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth-goerli.blockscout.com/
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
envFromSecret: envFromSecret:
NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID
......
...@@ -66,9 +66,9 @@ frontend: ...@@ -66,9 +66,9 @@ frontend:
NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-optimism-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: "true" NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com
NEXT_PUBLIC_L1_BASE_URL: https://blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
envFromSecret: envFromSecret:
......
# Deprecated environment variables
| Variable | Type | Description | Compulsoriness | Default value | Example value | Deprecated in version | Comment |
| --- | --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK | `boolean` | Set to true for optimistic L2 solutions | Required | - | `true` | v1.24.0 | Replaced by NEXT_PUBLIC_ROLLUP_TYPE |
| NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK | `boolean` | Set to true for zkevm L2 solutions | Required | - | `true` | v1.24.0 | Replaced by NEXT_PUBLIC_ROLLUP_TYPE |
| NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL |
| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L1_BASE_URL |
...@@ -34,8 +34,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -34,8 +34,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [Text ads](ENVS.md#text-ads) - [Text ads](ENVS.md#text-ads)
- [Beacon chain](ENVS.md#beacon-chain) - [Beacon chain](ENVS.md#beacon-chain)
- [User operations](ENVS.md#user-operations-feature-erc-4337) - [User operations](ENVS.md#user-operations-feature-erc-4337)
- [Optimistic rollup (L2) chain](ENVS.md#optimistic-rollup-l2-chain) - [Rollup chain](ENVS.md#rollup-chain)
- [ZkEvm rollup (L2) chain](NVS.md#zkevm-rollup-l2-chain)
- [Export data to CSV file](ENVS.md#export-data-to-csv-file) - [Export data to CSV file](ENVS.md#export-data-to-csv-file)
- [Google analytics](ENVS.md#google-analytics) - [Google analytics](ENVS.md#google-analytics)
- [Mixpanel analytics](ENVS.md#mixpanel-analytics) - [Mixpanel analytics](ENVS.md#mixpanel-analytics)
...@@ -332,7 +331,7 @@ This feature is **enabled by default** with the `slise` ads provider. To switch ...@@ -332,7 +331,7 @@ This feature is **enabled by default** with the `slise` ads provider. To switch
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `none` | Ads provider | - | `slise` | `coinzilla` | | NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `hype` \| `none` | Ads provider | - | `slise` | `coinzilla` |
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` | | NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` |
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` | | NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` |
...@@ -365,22 +364,13 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi ...@@ -365,22 +364,13 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
&nbsp; &nbsp;
### Optimistic rollup (L2) chain ### Rollup chain
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK | `boolean` | Set to true for optimistic L2 solutions | Required | - | `true` | | NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'zkEvm' ` | Rollup chain type | Required | - | `'optimistic'` |
| NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | | NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` |
| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | | NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | - | - | `https://app.optimism.io/bridge/withdraw` |
&nbsp;
### ZkEvm rollup (L2) chain
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK | `boolean` | Set to true for zkevm L2 solutions | Required | - | `true` |
| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` |
&nbsp; &nbsp;
...@@ -438,7 +428,8 @@ This feature is **always enabled**, but you can configure its behavior by passin ...@@ -438,7 +428,8 @@ This feature is **always enabled**, but you can configure its behavior by passin
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | Required | - | `https://example.com/marketplace_config.json` | | NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app. Can be replaced with NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | Required | - | `https://example.com/marketplace_config.json` |
| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url. Can be used instead of NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | - | - | `https://admin-rs.services.blockscout.com` |
| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | Required | - | `https://airtable.com/shrqUAcjgGJ4jU88C` | | NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | Required | - | `https://airtable.com/shrqUAcjgGJ4jU88C` |
| NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM | `string` | Link to form where users can suggest ideas for the marketplace | - | - | `https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form` | | NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM | `string` | Link to form where users can suggest ideas for the marketplace | - | - | `https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form` |
| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | | NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` |
......
...@@ -34,7 +34,7 @@ describe('falsy query parameters', () => { ...@@ -34,7 +34,7 @@ describe('falsy query parameters', () => {
test('builds URL with array-like query parameters', () => { test('builds URL with array-like query parameters', () => {
const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: [ '0x11', '0x22' ], sort: 'asc' }); const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: [ '0x11', '0x22' ], sort: 'asc' });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx%5B0%5D=0x11&includeTx%5B1%5D=0x22&sort=asc'); expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=0x11%2C0x22&sort=asc');
}); });
test('builds URL for resource with custom API endpoint', () => { test('builds URL for resource with custom API endpoint', () => {
......
...@@ -19,13 +19,7 @@ export default function buildUrl<R extends ResourceName>( ...@@ -19,13 +19,7 @@ export default function buildUrl<R extends ResourceName>(
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
// there are some pagination params that can be null or false for the next page // there are some pagination params that can be null or false for the next page
if (value !== undefined && value !== '') { value !== undefined && value !== '' && url.searchParams.append(key, String(value));
if (Array.isArray(value)) {
value.forEach((v, i) => url.searchParams.append(`${ key }[${ i }]`, String(v)));
} else {
url.searchParams.append(key, String(value));
}
}
}); });
return url.toString(); return url.toString();
......
...@@ -47,11 +47,14 @@ import type { ...@@ -47,11 +47,14 @@ import type {
} from 'types/api/ens'; } from 'types/api/ens';
import type { IndexingStatus } from 'types/api/indexingStatus'; import type { IndexingStatus } from 'types/api/indexingStatus';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { L2DepositsResponse, L2DepositsItem } from 'types/api/l2Deposits';
import type { L2OutputRootsResponse } from 'types/api/l2OutputRoots';
import type { L2TxnBatchesResponse } from 'types/api/l2TxnBatches';
import type { L2WithdrawalsResponse } from 'types/api/l2Withdrawals';
import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
import type {
OptimisticL2DepositsResponse,
OptimisticL2DepositsItem,
OptimisticL2OutputRootsResponse,
OptimisticL2TxnBatchesResponse,
OptimisticL2WithdrawalsResponse,
} from 'types/api/optimisticL2';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search'; import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
...@@ -81,11 +84,15 @@ import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'ty ...@@ -81,11 +84,15 @@ import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'ty
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts'; import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { VisualizedContract } from 'types/api/visualization'; import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches'; import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
import type { ArrayElement } from 'types/utils'; import type { ArrayElement } from 'types/utils';
import config from 'configs/app'; import config from 'configs/app';
const marketplaceFeature = getFeaturePayload(config.features.marketplace);
const marketplaceApi = marketplaceFeature && 'api' in marketplaceFeature ? marketplaceFeature.api : undefined;
export interface ApiResource { export interface ApiResource {
path: ResourcePath; path: ResourcePath;
endpoint?: string; endpoint?: string;
...@@ -221,6 +228,20 @@ export const RESOURCES = { ...@@ -221,6 +228,20 @@ export const RESOURCES = {
basePath: getFeaturePayload(config.features.sol2uml)?.api.basePath, basePath: getFeaturePayload(config.features.sol2uml)?.api.basePath,
}, },
// MARKETPLACE
marketplace_dapps: {
path: '/api/v1/chains/:chainId/marketplace/dapps',
pathParams: [ 'chainId' as const ],
endpoint: marketplaceApi?.endpoint,
basePath: marketplaceApi?.basePath,
},
marketplace_dapp: {
path: '/api/v1/chains/:chainId/marketplace/dapps/:dappId',
pathParams: [ 'chainId' as const, 'dappId' as const ],
endpoint: marketplaceApi?.endpoint,
basePath: marketplaceApi?.basePath,
},
// BLOCKS, TXS // BLOCKS, TXS
blocks: { blocks: {
path: '/api/v2/blocks', path: '/api/v2/blocks',
...@@ -688,7 +709,7 @@ Q extends 'homepage_chart_market' ? ChartMarketResponse : ...@@ -688,7 +709,7 @@ Q extends 'homepage_chart_market' ? ChartMarketResponse :
Q extends 'homepage_blocks' ? Array<Block> : Q extends 'homepage_blocks' ? Array<Block> :
Q extends 'homepage_txs' ? Array<Transaction> : Q extends 'homepage_txs' ? Array<Transaction> :
Q extends 'homepage_txs_watchlist' ? Array<Transaction> : Q extends 'homepage_txs_watchlist' ? Array<Transaction> :
Q extends 'homepage_deposits' ? Array<L2DepositsItem> : Q extends 'homepage_deposits' ? Array<OptimisticL2DepositsItem> :
Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> } : Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> } :
Q extends 'homepage_indexing_status' ? IndexingStatus : Q extends 'homepage_indexing_status' ? IndexingStatus :
Q extends 'homepage_zkevm_latest_batch' ? number : Q extends 'homepage_zkevm_latest_batch' ? number :
...@@ -752,10 +773,10 @@ Q extends 'visualize_sol2uml' ? VisualizedContract : ...@@ -752,10 +773,10 @@ Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig : Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
Q extends 'withdrawals' ? WithdrawalsResponse : Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters : Q extends 'withdrawals_counters' ? WithdrawalsCounters :
Q extends 'l2_output_roots' ? L2OutputRootsResponse : Q extends 'l2_output_roots' ? OptimisticL2OutputRootsResponse :
Q extends 'l2_withdrawals' ? L2WithdrawalsResponse : Q extends 'l2_withdrawals' ? OptimisticL2WithdrawalsResponse :
Q extends 'l2_deposits' ? L2DepositsResponse : Q extends 'l2_deposits' ? OptimisticL2DepositsResponse :
Q extends 'l2_txn_batches' ? L2TxnBatchesResponse : Q extends 'l2_txn_batches' ? OptimisticL2TxnBatchesResponse :
Q extends 'l2_output_roots_count' ? number : Q extends 'l2_output_roots_count' ? number :
Q extends 'l2_withdrawals_count' ? number : Q extends 'l2_withdrawals_count' ? number :
Q extends 'l2_deposits_count' ? number : Q extends 'l2_deposits_count' ? number :
...@@ -772,6 +793,8 @@ Q extends 'domains_lookup' ? EnsDomainLookupResponse : ...@@ -772,6 +793,8 @@ Q extends 'domains_lookup' ? EnsDomainLookupResponse :
Q extends 'user_ops' ? UserOpsResponse : Q extends 'user_ops' ? UserOpsResponse :
Q extends 'user_op' ? UserOp : Q extends 'user_op' ? UserOp :
Q extends 'user_ops_account' ? UserOpsAccount : Q extends 'user_ops_account' ? UserOpsAccount :
Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> :
Q extends 'marketplace_dapp' ? MarketplaceAppOverview :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
...@@ -67,7 +67,9 @@ export default function useNavItems(): ReturnType { ...@@ -67,7 +67,9 @@ export default function useNavItems(): ReturnType {
isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]', isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]',
} : null; } : null;
if (config.features.zkEvmRollup.isEnabled) { const rollupFeature = config.features.rollup;
if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') {
blockchainNavItems = [ blockchainNavItems = [
[ [
txs, txs,
...@@ -86,7 +88,7 @@ export default function useNavItems(): ReturnType { ...@@ -86,7 +88,7 @@ export default function useNavItems(): ReturnType {
ensLookup, ensLookup,
].filter(Boolean), ].filter(Boolean),
]; ];
} else if (config.features.optimisticRollup.isEnabled) { } else if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
blockchainNavItems = [ blockchainNavItems = [
[ [
txs, txs,
......
...@@ -5,8 +5,11 @@ import type { Route } from 'nextjs-routes'; ...@@ -5,8 +5,11 @@ import type { Route } from 'nextjs-routes';
import generate from './generate'; import generate from './generate';
export default function update<R extends Route>(route: R, apiData: ApiData<R>) { export default function update<R extends Route>(route: R, apiData: ApiData<R>) {
const { title, description } = generate(route, apiData); const { title, description, opengraph } = generate(route, apiData);
window.document.title = title; window.document.title = title;
window.document.querySelector('meta[name="description"]')?.setAttribute('content', description); window.document.querySelector('meta[name="description"]')?.setAttribute('content', description);
window.document.querySelector('meta[property="og:title"]')?.setAttribute('content', opengraph.title);
opengraph.description &&
window.document.querySelector('meta[property="og:description"]')?.setAttribute('content', opengraph.description);
} }
export default function shortenString(string: string | null) { export default function shortenString(string: string | null, charNumber: number | undefined = 8) {
if (!string) { if (!string) {
return ''; return '';
} }
if (string.length <= 7) { if (string.length <= charNumber) {
return string; return string;
} }
return string.slice(0, 4) + '...' + string.slice(-4); return string.slice(0, charNumber - 4) + '...' + string.slice(-4);
} }
...@@ -6,7 +6,7 @@ import type { SmartContractVerificationResponse } from 'types/api/contract'; ...@@ -6,7 +6,7 @@ import type { SmartContractVerificationResponse } from 'types/api/contract';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2TxnBatches'; import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2';
export type SocketMessageParams = SocketMessage.NewBlock | export type SocketMessageParams = SocketMessage.NewBlock |
SocketMessage.BlocksIndexStatus | SocketMessage.BlocksIndexStatus |
......
import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2TxnBatches'; import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2';
export const txnBatchData: ZkEvmL2TxnBatch = { export const txnBatchData: ZkEvmL2TxnBatch = {
acc_input_hash: '0x4bf88aabe33713b7817266d7860912c58272d808da7397cdc627ca53b296fad3', acc_input_hash: '0x4bf88aabe33713b7817266d7860912c58272d808da7397cdc627ca53b296fad3',
......
import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2TxnBatches'; import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2';
export const txnBatchesData: ZkEvmL2TxnBatchesResponse = { export const txnBatchesData: ZkEvmL2TxnBatchesResponse = {
items: [ items: [
......
...@@ -3,30 +3,52 @@ import sha256 from 'crypto-js/sha256'; ...@@ -3,30 +3,52 @@ import sha256 from 'crypto-js/sha256';
import type CspDev from 'csp-dev'; import type CspDev from 'csp-dev';
import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript'; import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript';
import { hypeInit } from 'ui/shared/ad/hypeBannerScript';
export function ad(): CspDev.DirectiveDescriptor { export function ad(): CspDev.DirectiveDescriptor {
return { return {
'connect-src': [ 'connect-src': [
// coinzilla
'coinzilla.com', 'coinzilla.com',
'*.coinzilla.com', '*.coinzilla.com',
'https://request-global.czilladx.com', 'https://request-global.czilladx.com',
// slise
'*.slise.xyz', '*.slise.xyz',
// hype
'api.hypelab.com',
], ],
'frame-src': [ 'frame-src': [
// coinzilla
'https://request-global.czilladx.com', 'https://request-global.czilladx.com',
], ],
'script-src': [ 'script-src': [
// coinzilla
'coinzillatag.com', 'coinzillatag.com',
// adbutler
'servedbyadbutler.com', 'servedbyadbutler.com',
`'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`, `'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`,
`'sha256-${ Base64.stringify(sha256(placeAd ?? '')) }'`, `'sha256-${ Base64.stringify(sha256(placeAd ?? '')) }'`,
// slise
'*.slise.xyz', '*.slise.xyz',
//hype
`'sha256-${ Base64.stringify(sha256(hypeInit ?? '')) }'`,
'https://api.hypelab.com',
'd1q98dzwj6s2rb.cloudfront.net',
], ],
'img-src': [ 'img-src': [
'servedbyadbutler.com', // coinzilla
'cdn.coinzilla.io', 'cdn.coinzilla.io',
// adbutler
'servedbyadbutler.com',
], ],
'font-src': [ 'font-src': [
// coinzilla
'https://request-global.czilladx.com', 'https://request-global.czilladx.com',
], ],
}; };
......
import type { GetServerSideProps } from 'next'; import type { GetServerSideProps } from 'next';
import config from 'configs/app'; import config from 'configs/app';
const rollupFeature = config.features.rollup;
export type Props = { export type Props = {
cookies: string; cookies: string;
...@@ -49,7 +50,10 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => { ...@@ -49,7 +50,10 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
}; };
export const withdrawals: GetServerSideProps<Props> = async(context) => { export const withdrawals: GetServerSideProps<Props> = async(context) => {
if (!config.features.beaconChain.isEnabled && !config.features.optimisticRollup.isEnabled) { if (
!config.features.beaconChain.isEnabled &&
!(rollupFeature.isEnabled && rollupFeature.type === 'optimistic')
) {
return { return {
notFound: true, notFound: true,
}; };
...@@ -59,7 +63,7 @@ export const withdrawals: GetServerSideProps<Props> = async(context) => { ...@@ -59,7 +63,7 @@ export const withdrawals: GetServerSideProps<Props> = async(context) => {
}; };
export const rollup: GetServerSideProps<Props> = async(context) => { export const rollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.optimisticRollup.isEnabled && !config.features.zkEvmRollup.isEnabled) { if (!config.features.rollup.isEnabled) {
return { return {
notFound: true, notFound: true,
}; };
...@@ -69,7 +73,7 @@ export const rollup: GetServerSideProps<Props> = async(context) => { ...@@ -69,7 +73,7 @@ export const rollup: GetServerSideProps<Props> = async(context) => {
}; };
export const optimisticRollup: GetServerSideProps<Props> = async(context) => { export const optimisticRollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.optimisticRollup.isEnabled) { if (!(rollupFeature.isEnabled && rollupFeature.type === 'optimistic')) {
return { return {
notFound: true, notFound: true,
}; };
...@@ -79,7 +83,7 @@ export const optimisticRollup: GetServerSideProps<Props> = async(context) => { ...@@ -79,7 +83,7 @@ export const optimisticRollup: GetServerSideProps<Props> = async(context) => {
}; };
export const zkEvmRollup: GetServerSideProps<Props> = async(context) => { export const zkEvmRollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.zkEvmRollup.isEnabled) { if (!(rollupFeature.isEnabled && rollupFeature.type === 'zkEvm')) {
return { return {
notFound: true, notFound: true,
}; };
......
...@@ -38,9 +38,9 @@ declare module "nextjs-routes" { ...@@ -38,9 +38,9 @@ declare module "nextjs-routes" {
| StaticRoute<"/login"> | StaticRoute<"/login">
| DynamicRoute<"/name-domains/[name]", { "name": string }> | DynamicRoute<"/name-domains/[name]", { "name": string }>
| StaticRoute<"/name-domains"> | StaticRoute<"/name-domains">
| StaticRoute<"/output-roots">
| DynamicRoute<"/op/[hash]", { "hash": string }> | DynamicRoute<"/op/[hash]", { "hash": string }>
| StaticRoute<"/ops"> | StaticRoute<"/ops">
| StaticRoute<"/output-roots">
| StaticRoute<"/search-results"> | StaticRoute<"/search-results">
| StaticRoute<"/stats"> | StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }> | DynamicRoute<"/token/[hash]", { "hash": string }>
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw", "test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.sh", "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.sh",
"test:pw:ci": "yarn test:pw --project=$PW_PROJECT", "test:pw:ci": "yarn test:pw --project=$PW_PROJECT",
"test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js",
"test:jest": "jest", "test:jest": "jest",
"test:jest:watch": "jest --watch", "test:jest:watch": "jest --watch",
"favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh" "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh"
...@@ -36,6 +37,7 @@ ...@@ -36,6 +37,7 @@
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4", "@emotion/styled": "^11.10.4",
"@growthbook/growthbook-react": "0.21.0", "@growthbook/growthbook-react": "0.21.0",
"@hypelab/sdk-react": "^1.0.0",
"@metamask/post-message-stream": "^7.0.0", "@metamask/post-message-stream": "^7.0.0",
"@metamask/providers": "^10.2.1", "@metamask/providers": "^10.2.1",
"@monaco-editor/react": "^4.4.6", "@monaco-editor/react": "^4.4.6",
......
...@@ -5,12 +5,19 @@ import React from 'react'; ...@@ -5,12 +5,19 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app'; import config from 'configs/app';
const rollupFeature = config.features.rollup;
const Batches = dynamic(() => { const Batches = dynamic(() => {
if (config.features.zkEvmRollup.isEnabled) { if (!rollupFeature.isEnabled) {
return import('ui/pages/ZkEvmL2TxnBatches'); throw new Error('Rollup feature is not enabled.');
}
switch (rollupFeature.type) {
case 'zkEvm':
return import('ui/pages/ZkEvmL2TxnBatches');
case 'optimistic':
return import('ui/pages/OptimisticL2TxnBatches');
} }
return import('ui/pages/L2TxnBatches');
}, { ssr: false }); }, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
......
...@@ -4,7 +4,7 @@ import React from 'react'; ...@@ -4,7 +4,7 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const Deposits = dynamic(() => import('ui/pages/L2Deposits'), { ssr: false }); const Deposits = dynamic(() => import('ui/pages/OptimisticL2Deposits'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
......
...@@ -4,7 +4,7 @@ import React from 'react'; ...@@ -4,7 +4,7 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const OutputRoots = dynamic(() => import('ui/pages/L2OutputRoots'), { ssr: false }); const OutputRoots = dynamic(() => import('ui/pages/OptimisticL2OutputRoots'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
......
...@@ -5,12 +5,18 @@ import React from 'react'; ...@@ -5,12 +5,18 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app'; import config from 'configs/app';
const rollupFeature = config.features.rollup;
const beaconChainFeature = config.features.beaconChain;
const Withdrawals = dynamic(() => { const Withdrawals = dynamic(() => {
if (config.features.optimisticRollup.isEnabled) { if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
return import('ui/pages/L2Withdrawals'); return import('ui/pages/OptimisticL2Withdrawals');
} }
return import('ui/pages/Withdrawals'); if (beaconChainFeature.isEnabled) {
return import('ui/pages/BeaconChainWithdrawals');
}
throw new Error('Withdrawals feature is not enabled.');
}, { ssr: false }); }, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
......
...@@ -15,9 +15,9 @@ export const featureEnvs = { ...@@ -15,9 +15,9 @@ export const featureEnvs = {
{ name: 'NEXT_PUBLIC_HAS_BEACON_CHAIN', value: 'true' }, { name: 'NEXT_PUBLIC_HAS_BEACON_CHAIN', value: 'true' },
], ],
optimisticRollup: [ optimisticRollup: [
{ name: 'NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', value: 'true' }, { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'optimistic' },
{ name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
{ name: 'NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' }, { name: 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' },
], ],
bridgedTokens: [ bridgedTokens: [
{ {
...@@ -32,9 +32,9 @@ export const featureEnvs = { ...@@ -32,9 +32,9 @@ export const featureEnvs = {
txInterpretation: [ txInterpretation: [
{ name: 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', value: 'blockscout' }, { name: 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', value: 'blockscout' },
], ],
zkRollup: [ zkEvmRollup: [
{ name: 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK', value: 'true' }, { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'zkEvm' },
{ name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
], ],
userOps: [ userOps: [
{ name: 'NEXT_PUBLIC_HAS_USER_OPS', value: 'true' }, { name: 'NEXT_PUBLIC_HAS_USER_OPS', value: 'true' },
......
import type { L2DepositsItem } from 'types/api/l2Deposits'; import type {
import type { L2OutputRootsItem } from 'types/api/l2OutputRoots'; OptimisticL2DepositsItem,
import type { L2TxnBatchesItem } from 'types/api/l2TxnBatches'; OptimisticL2OutputRootsItem,
import type { L2WithdrawalsItem } from 'types/api/l2Withdrawals'; OptimisticL2TxnBatchesItem,
OptimisticL2WithdrawalsItem,
} from 'types/api/optimisticL2';
import { ADDRESS_HASH, ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_HASH, ADDRESS_PARAMS } from './addressParams';
import { TX_HASH } from './tx'; import { TX_HASH } from './tx';
export const L2_DEPOSIT_ITEM: L2DepositsItem = { export const L2_DEPOSIT_ITEM: OptimisticL2DepositsItem = {
l1_block_number: 9045233, l1_block_number: 9045233,
l1_block_timestamp: '2023-05-22T18:00:36.000000Z', l1_block_timestamp: '2023-05-22T18:00:36.000000Z',
l1_tx_hash: TX_HASH, l1_tx_hash: TX_HASH,
...@@ -15,7 +17,7 @@ export const L2_DEPOSIT_ITEM: L2DepositsItem = { ...@@ -15,7 +17,7 @@ export const L2_DEPOSIT_ITEM: L2DepositsItem = {
l2_tx_hash: TX_HASH, l2_tx_hash: TX_HASH,
}; };
export const L2_WITHDRAWAL_ITEM: L2WithdrawalsItem = { export const L2_WITHDRAWAL_ITEM: OptimisticL2WithdrawalsItem = {
challenge_period_end: null, challenge_period_end: null,
from: ADDRESS_PARAMS, from: ADDRESS_PARAMS,
l1_tx_hash: TX_HASH, l1_tx_hash: TX_HASH,
...@@ -26,7 +28,7 @@ export const L2_WITHDRAWAL_ITEM: L2WithdrawalsItem = { ...@@ -26,7 +28,7 @@ export const L2_WITHDRAWAL_ITEM: L2WithdrawalsItem = {
status: 'Ready to prove', status: 'Ready to prove',
}; };
export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = { export const L2_TXN_BATCHES_ITEM: OptimisticL2TxnBatchesItem = {
epoch_number: 9103513, epoch_number: 9103513,
l1_timestamp: '2023-06-01T14:46:48.000000Z', l1_timestamp: '2023-06-01T14:46:48.000000Z',
l1_tx_hashes: [ l1_tx_hashes: [
...@@ -36,7 +38,7 @@ export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = { ...@@ -36,7 +38,7 @@ export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = {
tx_count: 9, tx_count: 9,
}; };
export const L2_OUTPUT_ROOTS_ITEM: L2OutputRootsItem = { export const L2_OUTPUT_ROOTS_ITEM: OptimisticL2OutputRootsItem = {
l1_block_number: 9103684, l1_block_number: 9103684,
l1_timestamp: '2023-06-01T15:26:12.000000Z', l1_timestamp: '2023-06-01T15:26:12.000000Z',
l1_tx_hash: TX_HASH, l1_tx_hash: TX_HASH,
......
...@@ -53,6 +53,10 @@ export const GET_TRANSACTION_RECEIPT: TransactionReceipt = { ...@@ -53,6 +53,10 @@ export const GET_TRANSACTION_RECEIPT: TransactionReceipt = {
export const GET_TRANSACTION_CONFIRMATIONS = BigInt(420); export const GET_TRANSACTION_CONFIRMATIONS = BigInt(420);
export const GET_BALANCE = BigInt(42_000_000_000_000);
export const GET_TRANSACTIONS_COUNT = 42;
export const GET_BLOCK: GetBlockReturnType<Chain, false, 'latest'> = { export const GET_BLOCK: GetBlockReturnType<Chain, false, 'latest'> = {
baseFeePerGas: BigInt(11), baseFeePerGas: BigInt(11),
difficulty: BigInt(111), difficulty: BigInt(111),
......
import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2TxnBatches'; import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2';
import { TX_HASH } from './tx'; import { TX_HASH } from './tx';
......
...@@ -10,7 +10,73 @@ dotenv \ ...@@ -10,7 +10,73 @@ dotenv \
yarn svg:build-sprite yarn svg:build-sprite
# Check if the "--affected" argument is present in the script args
check_affected_flag() {
local affected_flag=false
for arg in "$@"; do
if [[ "$arg" = "--affected"* ]]; then
# Extract the value after the equals sign
is_affected_value=${is_affected_arg#*=}
if [ "$is_affected_value" != "false" ]; then
affected_flag=true
fi
break
fi
done
echo "$affected_flag"
}
# Remove the "--affected" argument from the script args
filter_arguments() {
local args=()
for arg in "$@"; do
if [[ "$arg" != "--affected"* ]]; then
args+=("$arg")
fi
done
echo "${args[@]}"
}
get_files_to_run() {
local is_affected=$1
local files_to_run=""
if [ "$is_affected" = true ]; then
affected_tests_file="./playwright/affected-tests.txt"
if [ -f "$affected_tests_file" ]; then
file_content=$(<"$affected_tests_file")
files_to_run="${file_content//$'\n'/$' '}"
if [ -z "$files_to_run" ]; then
exit 1
fi
fi
fi
echo "$files_to_run"
}
args=$(filter_arguments "$@")
affected_flag=$(check_affected_flag "$@")
files_to_run=$(get_files_to_run "$affected_flag")
if [ $? -eq 1 ]; then
echo "No affected tests found in the file. Exiting..."
exit 0
fi
echo "Running Playwright tests with the following arguments: $args"
echo "Affected flag: $affected_flag"
echo "Files to run: $files_to_run"
dotenv \ dotenv \
-v NODE_OPTIONS=\"--max-old-space-size=4096\" \ -v NODE_OPTIONS=\"--max-old-space-size=4096\" \
-e $config_file \ -e $config_file \
-- playwright test -c playwright-ct.config.ts "$@" -- playwright test -c playwright-ct.config.ts $files_to_run $args
...@@ -39,7 +39,7 @@ export interface Address extends UserTags { ...@@ -39,7 +39,7 @@ export interface Address extends UserTags {
export interface AddressCounters { export interface AddressCounters {
transactions_count: string; transactions_count: string;
token_transfers_count: string; token_transfers_count: string;
gas_usage_count: string; gas_usage_count: string | null;
validations_count: string | null; validations_count: string | null;
} }
......
export type L2DepositsItem = {
l1_block_number: number;
l1_tx_hash: string;
l1_block_timestamp: string;
l1_tx_origin: string;
l2_tx_gas_limit: string;
l2_tx_hash: string;
}
export type L2DepositsResponse = {
items: Array<L2DepositsItem>;
next_page_params: {
items_count: number;
l1_block_number: number;
tx_hash: string;
};
}
export type L2OutputRootsItem = {
l1_block_number: number;
l1_timestamp: string;
l1_tx_hash: string;
l2_block_number: number;
l2_output_index: number;
output_root: string;
}
export type L2OutputRootsResponse = {
items: Array<L2OutputRootsItem>;
next_page_params: {
index: number;
items_count: number;
};
}
export type L2TxnBatchesItem = {
epoch_number: number;
l1_tx_hashes: Array<string>;
l1_timestamp: string;
l2_block_number: number;
tx_count: number;
}
export type L2TxnBatchesResponse = {
items: Array<L2TxnBatchesItem>;
next_page_params: {
block_number: number;
items_count: number;
};
}
import type { AddressParam } from './addressParams';
export type L2WithdrawalsItem = {
'challenge_period_end': string | null;
'from': AddressParam | null;
'l1_tx_hash': string | null;
'l2_timestamp': string | null;
'l2_tx_hash': string;
'msg_nonce': number;
'msg_nonce_version': number;
'status': string;
}
export const WITHDRAWAL_STATUSES = [
'Waiting for state root',
'Ready to prove',
'In challenge period',
'Ready for relay',
'Relayed',
] as const;
export type L2WithdrawalStatus = typeof WITHDRAWAL_STATUSES[number];
export type L2WithdrawalsResponse = {
items: Array<L2WithdrawalsItem>;
'next_page_params': {
'items_count': number;
'nonce': string;
};
}
import type { AddressParam } from './addressParams';
export type OptimisticL2DepositsItem = {
l1_block_number: number;
l1_tx_hash: string;
l1_block_timestamp: string;
l1_tx_origin: string;
l2_tx_gas_limit: string;
l2_tx_hash: string;
}
export type OptimisticL2DepositsResponse = {
items: Array<OptimisticL2DepositsItem>;
next_page_params: {
items_count: number;
l1_block_number: number;
tx_hash: string;
};
}
export type OptimisticL2OutputRootsItem = {
l1_block_number: number;
l1_timestamp: string;
l1_tx_hash: string;
l2_block_number: number;
l2_output_index: number;
output_root: string;
}
export type OptimisticL2OutputRootsResponse = {
items: Array<OptimisticL2OutputRootsItem>;
next_page_params: {
index: number;
items_count: number;
};
}
export type OptimisticL2TxnBatchesItem = {
epoch_number: number;
l1_tx_hashes: Array<string>;
l1_timestamp: string;
l2_block_number: number;
tx_count: number;
}
export type OptimisticL2TxnBatchesResponse = {
items: Array<OptimisticL2TxnBatchesItem>;
next_page_params: {
block_number: number;
items_count: number;
};
}
export type OptimisticL2WithdrawalsItem = {
'challenge_period_end': string | null;
'from': AddressParam | null;
'l1_tx_hash': string | null;
'l2_timestamp': string | null;
'l2_tx_hash': string;
'msg_nonce': number;
'msg_nonce_version': number;
'status': string;
}
export const WITHDRAWAL_STATUSES = [
'Waiting for state root',
'Ready to prove',
'In challenge period',
'Ready for relay',
'Relayed',
] as const;
export type OptimisticL2WithdrawalStatus = typeof WITHDRAWAL_STATUSES[number];
export type OptimisticL2WithdrawalsResponse = {
items: Array<OptimisticL2WithdrawalsItem>;
'next_page_params': {
'items_count': number;
'nonce': string;
};
}
...@@ -2,7 +2,7 @@ import type { AddressParam } from './addressParams'; ...@@ -2,7 +2,7 @@ import type { AddressParam } from './addressParams';
import type { BlockTransactionsResponse } from './block'; import type { BlockTransactionsResponse } from './block';
import type { DecodedInput } from './decodedInput'; import type { DecodedInput } from './decodedInput';
import type { Fee } from './fee'; import type { Fee } from './fee';
import type { L2WithdrawalStatus } from './l2Withdrawals'; import type { OptimisticL2WithdrawalStatus } from './optimisticL2';
import type { TokenInfo } from './token'; import type { TokenInfo } from './token';
import type { TokenTransfer } from './tokenTransfer'; import type { TokenTransfer } from './tokenTransfer';
import type { TxAction } from './txAction'; import type { TxAction } from './txAction';
...@@ -17,7 +17,7 @@ type WrappedTransactionFields = 'decoded_input' | 'fee' | 'gas_limit' | 'gas_pri ...@@ -17,7 +17,7 @@ type WrappedTransactionFields = 'decoded_input' | 'fee' | 'gas_limit' | 'gas_pri
export interface OpWithdrawal { export interface OpWithdrawal {
l1_transaction_hash: string; l1_transaction_hash: string;
nonce: number; nonce: number;
status: L2WithdrawalStatus; status: OptimisticL2WithdrawalStatus;
} }
export type Transaction = { export type Transaction = {
......
import type { ArrayElement } from 'types/utils'; import type { ArrayElement } from 'types/utils';
export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'none' ] as const; export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'hype', 'none' ] as const;
export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>; export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>;
export const SUPPORTED_AD_TEXT_PROVIDERS = [ 'coinzilla', 'none' ] as const; export const SUPPORTED_AD_TEXT_PROVIDERS = [ 'coinzilla', 'none' ] as const;
......
import type { ArrayElement } from 'types/utils';
export const ROLLUP_TYPES = [
'optimistic',
'zkEvm',
] as const;
export type RollupType = ArrayElement<typeof ROLLUP_TYPES>;
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { WindowProvider } from 'wagmi'; import type { WindowProvider } from 'wagmi';
import type { Address } from 'types/api/address';
import type { ResourceError } from 'lib/api/resources';
import * as addressMock from 'mocks/address/address'; import * as addressMock from 'mocks/address/address';
import * as countersMock from 'mocks/address/counters'; import * as countersMock from 'mocks/address/counters';
import * as tokensMock from 'mocks/address/tokens'; import * as tokensMock from 'mocks/address/tokens';
...@@ -15,6 +11,7 @@ import * as configs from 'playwright/utils/configs'; ...@@ -15,6 +11,7 @@ import * as configs from 'playwright/utils/configs';
import AddressDetails from './AddressDetails'; import AddressDetails from './AddressDetails';
import MockAddressPage from './testUtils/MockAddressPage'; import MockAddressPage from './testUtils/MockAddressPage';
import type { AddressQuery } from './utils/useAddressQuery';
const ADDRESS_HASH = addressMock.hash; const ADDRESS_HASH = addressMock.hash;
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH }); const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
...@@ -40,7 +37,7 @@ test('contract +@mobile', async({ mount, page }) => { ...@@ -40,7 +37,7 @@ test('contract +@mobile', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<AddressDetails addressQuery={{ data: addressMock.contract } as UseQueryResult<Address, ResourceError>}/> <AddressDetails addressQuery={{ data: addressMock.contract } as AddressQuery}/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -82,7 +79,7 @@ test('token', async({ mount, page }) => { ...@@ -82,7 +79,7 @@ test('token', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<MockAddressPage> <MockAddressPage>
<AddressDetails addressQuery={{ data: addressMock.token } as UseQueryResult<Address, ResourceError>}/> <AddressDetails addressQuery={{ data: addressMock.token } as AddressQuery}/>
</MockAddressPage> </MockAddressPage>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
...@@ -106,7 +103,7 @@ test('validator +@mobile', async({ mount, page }) => { ...@@ -106,7 +103,7 @@ test('validator +@mobile', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<AddressDetails addressQuery={{ data: addressMock.validator } as UseQueryResult<Address, ResourceError>}/> <AddressDetails addressQuery={{ data: addressMock.validator } as AddressQuery}/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
......
This diff is collapsed.
...@@ -108,7 +108,7 @@ test.describe('socket', () => { ...@@ -108,7 +108,7 @@ test.describe('socket', () => {
}, },
}; };
const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS }); const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS }) + '?type=';
await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({ await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({
status: 200, status: 200,
...@@ -144,7 +144,7 @@ test.describe('socket', () => { ...@@ -144,7 +144,7 @@ test.describe('socket', () => {
}, },
}; };
const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS }); const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS }) + '?type=';
await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({ await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({
status: 200, status: 200,
......
...@@ -13,8 +13,8 @@ import AddressTokens from './AddressTokens'; ...@@ -13,8 +13,8 @@ import AddressTokens from './AddressTokens';
const ADDRESS_HASH = addressMock.withName.hash; const ADDRESS_HASH = addressMock.withName.hash;
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH }); const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }); const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH });
const API_URL_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH }); const API_URL_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH }) + '?type=';
const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH }); const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH }) + '?type=';
const nextPageParams = { const nextPageParams = {
items_count: 50, items_count: 50,
......
...@@ -9,8 +9,8 @@ import ActionBar from 'ui/shared/ActionBar'; ...@@ -9,8 +9,8 @@ import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem'; import BeaconChainWithdrawalsListItem from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable'; import BeaconChainWithdrawalsTable from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsTable';
const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => { const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter(); const router = useRouter();
...@@ -32,7 +32,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE ...@@ -32,7 +32,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
{ data.items.map((item, index) => ( { data.items.map((item, index) => (
<WithdrawalsListItem <BeaconChainWithdrawalsListItem
key={ item.index + Number(isPlaceholderData ? index : '') } key={ item.index + Number(isPlaceholderData ? index : '') }
item={ item } item={ item }
view="address" view="address"
...@@ -41,7 +41,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE ...@@ -41,7 +41,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
)) } )) }
</Show> </Show>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } view="address" top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/> <BeaconChainWithdrawalsTable items={ data.items } view="address" top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide> </Hide>
</> </>
) : null ; ) : null ;
......
...@@ -2,6 +2,7 @@ import { Checkbox, Flex, chakra } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Checkbox, Flex, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import { getAddress } from 'viem';
import type { SmartContractMethodOutput } from 'types/api/contract'; import type { SmartContractMethodOutput } from 'types/api/contract';
...@@ -50,7 +51,7 @@ const ContractMethodStatic = ({ data }: Props) => { ...@@ -50,7 +51,7 @@ const ContractMethodStatic = ({ data }: Props) => {
if (typeof data.value === 'string' && data.type === 'address' && data.value) { if (typeof data.value === 'string' && data.type === 'address' && data.value) {
return ( return (
<AddressEntity <AddressEntity
address={{ hash: data.value }} address={{ hash: getAddress(data.value) }}
noIcon noIcon
/> />
); );
......
...@@ -16,6 +16,7 @@ interface Props { ...@@ -16,6 +16,7 @@ interface Props {
address: string; address: string;
onClick: () => void; onClick: () => void;
isAddressQueryLoading: boolean; isAddressQueryLoading: boolean;
isDegradedData: boolean;
} }
const PROP_TO_TAB = { const PROP_TO_TAB = {
...@@ -24,7 +25,7 @@ const PROP_TO_TAB = { ...@@ -24,7 +25,7 @@ const PROP_TO_TAB = {
validations_count: 'blocks_validated', validations_count: 'blocks_validated',
}; };
const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoading }: Props) => { const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoading, isDegradedData }: Props) => {
if (query.isPlaceholderData || isAddressQueryLoading) { if (query.isPlaceholderData || isAddressQueryLoading) {
return <Skeleton h={ 5 } w="80px" borderRadius="full"/>; return <Skeleton h={ 5 } w="80px" borderRadius="full"/>;
} }
...@@ -44,6 +45,11 @@ const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoadi ...@@ -44,6 +45,11 @@ const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoadi
if (data === '0') { if (data === '0') {
return <span>0</span>; return <span>0</span>;
} }
if (isDegradedData) {
return <span>{ Number(data).toLocaleString() }</span>;
}
return ( return (
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }) } onClick={ onClick }> <LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }) } onClick={ onClick }>
{ Number(data).toLocaleString() } { Number(data).toLocaleString() }
......
...@@ -45,7 +45,7 @@ const TxInternalsListItem = ({ ...@@ -45,7 +45,7 @@ const TxInternalsListItem = ({
hash={ txnHash } hash={ txnHash }
isLoading={ isLoading } isLoading={ isLoading }
fontWeight={ 700 } fontWeight={ 700 }
truncation="constant" truncation="constant_long"
/> />
<Skeleton isLoaded={ !isLoading } color="text_secondary" fontWeight="400" fontSize="sm"> <Skeleton isLoaded={ !isLoading } color="text_secondary" fontWeight="400" fontSize="sm">
<span>{ dayjs(timestamp).fromNow() }</span> <span>{ dayjs(timestamp).fromNow() }</span>
......
...@@ -43,6 +43,7 @@ const AddressIntTxsTableItem = ({ ...@@ -43,6 +43,7 @@ const AddressIntTxsTableItem = ({
isLoading={ isLoading } isLoading={ isLoading }
fontWeight={ 700 } fontWeight={ 700 }
noIcon noIcon
truncation="constant_long"
/> />
{ timestamp && ( { timestamp && (
<Skeleton isLoaded={ !isLoading } color="text_secondary" fontWeight="400" fontSize="sm"> <Skeleton isLoaded={ !isLoading } color="text_secondary" fontWeight="400" fontSize="sm">
......
...@@ -43,6 +43,7 @@ const ERC20TokensTableItem = ({ ...@@ -43,6 +43,7 @@ const ERC20TokensTableItem = ({
<AddressEntity <AddressEntity
address={{ hash: token.address }} address={{ hash: token.address }}
isLoading={ isLoading } isLoading={ isLoading }
truncation="constant"
noIcon noIcon
/> />
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0"/> <AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0"/>
......
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import type { AddressCounters } from 'types/api/address';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import { publicClient } from 'lib/web3/client';
import { ADDRESS_COUNTERS } from 'stubs/address';
import { GET_TRANSACTIONS_COUNT } from 'stubs/RPC';
import type { AddressQuery } from './useAddressQuery';
type RpcResponseType = [
number | null,
];
export type AddressCountersQuery = UseQueryResult<AddressCounters, ResourceError<{ status: number }>> & {
isDegradedData: boolean;
};
interface Params {
hash: string;
addressQuery: AddressQuery;
}
export default function useAddressQuery({ hash, addressQuery }: Params): AddressCountersQuery {
const enabled = Boolean(hash) && !addressQuery.isPlaceholderData;
const apiQuery = useApiQuery<'address_counters', { status: number }>('address_counters', {
pathParams: { hash },
queryOptions: {
enabled: enabled && !addressQuery.isDegradedData,
placeholderData: ADDRESS_COUNTERS,
refetchOnMount: false,
},
});
const rpcQuery = useQuery<RpcResponseType, unknown, AddressCounters | null>({
queryKey: [ 'RPC', 'address_counters', { hash } ],
queryFn: async() => {
const txCount = publicClient.getTransactionCount({ address: hash as `0x${ string }` }).catch(() => null);
return Promise.all([
txCount,
]);
},
select: (response) => {
const [ txCount ] = response;
return {
transactions_count: txCount?.toString() ?? '0',
token_transfers_count: '0',
gas_usage_count: null,
validations_count: null,
};
},
placeholderData: [ GET_TRANSACTIONS_COUNT ],
enabled: enabled && (addressQuery.isDegradedData || apiQuery.isError),
retry: false,
refetchOnMount: false,
});
const isRpcQuery = Boolean((addressQuery.isDegradedData || apiQuery.isError) && rpcQuery.data);
const query = isRpcQuery ? rpcQuery as UseQueryResult<AddressCounters, ResourceError<{ status: number }>> : apiQuery;
return {
...query,
isDegradedData: isRpcQuery,
};
}
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { Address } from 'types/api/address';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import { retry } from 'lib/api/useQueryClientConfig';
import { SECOND } from 'lib/consts';
import { publicClient } from 'lib/web3/client';
import { ADDRESS_INFO } from 'stubs/address';
import { GET_BALANCE } from 'stubs/RPC';
type RpcResponseType = [
bigint | null,
];
export type AddressQuery = UseQueryResult<Address, ResourceError<{ status: number }>> & {
isDegradedData: boolean;
};
interface Params {
hash: string;
}
export default function useAddressQuery({ hash }: Params): AddressQuery {
const [ isRefetchEnabled, setRefetchEnabled ] = React.useState(false);
const apiQuery = useApiQuery<'address', { status: number }>('address', {
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash),
placeholderData: ADDRESS_INFO,
refetchOnMount: false,
retry: (failureCount, error) => {
if (isRefetchEnabled) {
return false;
}
return retry(failureCount, error);
},
refetchInterval: (): number | false => {
return isRefetchEnabled ? 15 * SECOND : false;
},
},
});
const rpcQuery = useQuery<RpcResponseType, unknown, Address | null>({
queryKey: [ 'RPC', 'address', { hash } ],
queryFn: async() => {
if (!publicClient) {
throw new Error('No public RPC client');
}
const balance = publicClient.getBalance({ address: hash as `0x${ string }` }).catch(() => null);
return Promise.all([
balance,
]);
},
select: (response) => {
const [ balance ] = response;
if (!balance) {
return null;
}
return {
hash,
block_number_balance_updated_at: null,
coin_balance: balance.toString(),
creator_address_hash: null,
creation_tx_hash: null,
exchange_rate: null,
ens_domain_name: null,
has_custom_methods_read: false,
has_custom_methods_write: false,
has_decompiled_code: false,
has_logs: false,
has_methods_read: false,
has_methods_read_proxy: false,
has_methods_write: false,
has_methods_write_proxy: false,
has_token_transfers: false,
has_tokens: false,
has_validated_blocks: false,
implementation_address: null,
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
token: null,
watchlist_address_id: null,
private_tags: null,
public_tags: null,
watchlist_names: null,
};
},
placeholderData: [ GET_BALANCE ],
enabled: apiQuery.isError || apiQuery.errorUpdateCount > 0,
retry: false,
refetchOnMount: false,
});
React.useEffect(() => {
if (apiQuery.isPlaceholderData || !publicClient) {
return;
}
if (apiQuery.isError && apiQuery.errorUpdateCount === 1) {
setRefetchEnabled(true);
} else if (!apiQuery.isError) {
setRefetchEnabled(false);
}
}, [ apiQuery.errorUpdateCount, apiQuery.isError, apiQuery.isPlaceholderData ]);
React.useEffect(() => {
if (!rpcQuery.isPlaceholderData && !rpcQuery.data) {
setRefetchEnabled(false);
}
}, [ rpcQuery.data, rpcQuery.isPlaceholderData ]);
const isRpcQuery = Boolean((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0 && rpcQuery.data && publicClient);
const query = isRpcQuery ? rpcQuery as UseQueryResult<Address, ResourceError<{ status: number }>> : apiQuery;
return {
...query,
isDegradedData: isRpcQuery,
};
}
...@@ -157,7 +157,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -157,7 +157,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
} }
case 'INVALID_SIGNER_ERROR': { case 'INVALID_SIGNER_ERROR': {
const signer = shortenString(formState.errors.root.message || ''); const signer = shortenString(formState.errors.root.message || '');
const expectedSigners = [ contractCreator, contractOwner ].filter(Boolean).map(shortenString).join(', '); const expectedSigners = [ contractCreator, contractOwner ].filter(Boolean).map(s => shortenString(s)).join(', ');
return ( return (
<Box> <Box>
<span>This address </span> <span>This address </span>
......
...@@ -35,6 +35,7 @@ const AddressesListItem = ({ ...@@ -35,6 +35,7 @@ const AddressesListItem = ({
isLoading={ isLoading } isLoading={ isLoading }
fontWeight={ 700 } fontWeight={ 700 }
mr={ 2 } mr={ 2 }
truncation="constant"
/> />
<Skeleton isLoaded={ !isLoading } fontSize="sm" ml="auto" minW={ 6 } color="text_secondary"> <Skeleton isLoaded={ !isLoading } fontSize="sm" ml="auto" minW={ 6 } color="text_secondary">
<span>{ index }</span> <span>{ index }</span>
......
...@@ -34,7 +34,7 @@ interface Props { ...@@ -34,7 +34,7 @@ interface Props {
query: BlockQuery; query: BlockQuery;
} }
const isRollup = config.features.optimisticRollup.isEnabled || config.features.zkEvmRollup.isEnabled; const rollupFeature = config.features.rollup;
const BlockDetails = ({ query }: Props) => { const BlockDetails = ({ query }: Props) => {
const [ isExpanded, setIsExpanded ] = React.useState(false); const [ isExpanded, setIsExpanded ] = React.useState(false);
...@@ -73,7 +73,7 @@ const BlockDetails = ({ query }: Props) => { ...@@ -73,7 +73,7 @@ const BlockDetails = ({ query }: Props) => {
const validatorTitle = getNetworkValidatorTitle(); const validatorTitle = getNetworkValidatorTitle();
const rewardBreakDown = (() => { const rewardBreakDown = (() => {
if (isRollup || totalReward.isEqualTo(ZERO) || txFees.isEqualTo(ZERO) || burntFees.isEqualTo(ZERO)) { if (rollupFeature.isEnabled || totalReward.isEqualTo(ZERO) || txFees.isEqualTo(ZERO) || burntFees.isEqualTo(ZERO)) {
return null; return null;
} }
...@@ -107,7 +107,7 @@ const BlockDetails = ({ query }: Props) => { ...@@ -107,7 +107,7 @@ const BlockDetails = ({ query }: Props) => {
})(); })();
const verificationTitle = (() => { const verificationTitle = (() => {
if (config.features.zkEvmRollup.isEnabled) { if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') {
return 'Sequenced by'; return 'Sequenced by';
} }
...@@ -205,7 +205,7 @@ const BlockDetails = ({ query }: Props) => { ...@@ -205,7 +205,7 @@ const BlockDetails = ({ query }: Props) => {
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ } { /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ !isRollup && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && ( { !rollupFeature.isEnabled && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && (
<DetailsInfoItem <DetailsInfoItem
title="Block reward" title="Block reward"
hint={ hint={
......
...@@ -3,8 +3,8 @@ import React from 'react'; ...@@ -3,8 +3,8 @@ import React from 'react';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import WithdrawalsList from 'ui/withdrawals/WithdrawalsList'; import BeaconChainWithdrawalsList from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsList';
import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable'; import BeaconChainWithdrawalsTable from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsTable';
type Props = { type Props = {
blockWithdrawalsQuery: QueryWithPagesResult<'block_withdrawals'>; blockWithdrawalsQuery: QueryWithPagesResult<'block_withdrawals'>;
...@@ -14,14 +14,14 @@ const BlockWithdrawals = ({ blockWithdrawalsQuery }: Props) => { ...@@ -14,14 +14,14 @@ const BlockWithdrawals = ({ blockWithdrawalsQuery }: Props) => {
const content = blockWithdrawalsQuery.data?.items ? ( const content = blockWithdrawalsQuery.data?.items ? (
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
<WithdrawalsList <BeaconChainWithdrawalsList
items={ blockWithdrawalsQuery.data.items } items={ blockWithdrawalsQuery.data.items }
isLoading={ blockWithdrawalsQuery.isPlaceholderData } isLoading={ blockWithdrawalsQuery.isPlaceholderData }
view="block" view="block"
/> />
</Show> </Show>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<WithdrawalsTable <BeaconChainWithdrawalsTable
items={ blockWithdrawalsQuery.data.items } items={ blockWithdrawalsQuery.data.items }
isLoading={ blockWithdrawalsQuery.isPlaceholderData } isLoading={ blockWithdrawalsQuery.isPlaceholderData }
top={ blockWithdrawalsQuery.pagination.isVisible ? 80 : 0 } top={ blockWithdrawalsQuery.pagination.isVisible ? 80 : 0 }
......
...@@ -28,7 +28,7 @@ interface Props { ...@@ -28,7 +28,7 @@ interface Props {
enableTimeIncrement?: boolean; enableTimeIncrement?: boolean;
} }
const isRollup = config.features.optimisticRollup.isEnabled || config.features.zkEvmRollup.isEnabled; const isRollup = config.features.rollup.isEnabled;
const BlocksListItem = ({ data, isLoading, enableTimeIncrement }: Props) => { const BlocksListItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
const totalReward = getBlockTotalReward(data); const totalReward = getBlockTotalReward(data);
...@@ -63,6 +63,7 @@ const BlocksListItem = ({ data, isLoading, enableTimeIncrement }: Props) => { ...@@ -63,6 +63,7 @@ const BlocksListItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
<AddressEntity <AddressEntity
address={ data.miner } address={ data.miner }
isLoading={ isLoading } isLoading={ isLoading }
truncation="constant"
/> />
</Flex> </Flex>
) } ) }
......
...@@ -28,7 +28,7 @@ const GAS_COL_WEIGHT = 33; ...@@ -28,7 +28,7 @@ const GAS_COL_WEIGHT = 33;
const REWARD_COL_WEIGHT = 22; const REWARD_COL_WEIGHT = 22;
const FEES_COL_WEIGHT = 22; const FEES_COL_WEIGHT = 22;
const isRollup = config.features.optimisticRollup.isEnabled || config.features.zkEvmRollup.isEnabled; const isRollup = config.features.rollup.isEnabled;
const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum, socketInfoAlert }: Props) => { const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum, socketInfoAlert }: Props) => {
......
...@@ -25,7 +25,7 @@ interface Props { ...@@ -25,7 +25,7 @@ interface Props {
enableTimeIncrement?: boolean; enableTimeIncrement?: boolean;
} }
const isRollup = config.features.optimisticRollup.isEnabled || config.features.zkEvmRollup.isEnabled; const isRollup = config.features.rollup.isEnabled;
const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
const totalReward = getBlockTotalReward(data); const totalReward = getBlockTotalReward(data);
...@@ -70,6 +70,7 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { ...@@ -70,6 +70,7 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
<AddressEntity <AddressEntity
address={ data.miner } address={ data.miner }
isLoading={ isLoading } isLoading={ isLoading }
truncation="constant"
/> />
</Td> </Td>
) } ) }
......
...@@ -2,7 +2,7 @@ import { Skeleton } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2';
import config from 'configs/app'; import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
...@@ -12,14 +12,14 @@ import TxEntity from 'ui/shared/entities/tx/TxEntity'; ...@@ -12,14 +12,14 @@ import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
const feature = config.features.optimisticRollup; const rollupFeature = config.features.rollup;
type Props = { item: L2DepositsItem; isLoading?: boolean }; type Props = { item: OptimisticL2DepositsItem; isLoading?: boolean };
const DepositsListItem = ({ item, isLoading }: Props) => { const OptimisticDepositsListItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.l1_block_timestamp).fromNow(); const timeAgo = dayjs(item.l1_block_timestamp).fromNow();
if (!feature.isEnabled) { if (!rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
return null; return null;
} }
...@@ -44,6 +44,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => { ...@@ -44,6 +44,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
hash={ item.l2_tx_hash } hash={ item.l2_tx_hash }
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
truncation="constant_long"
/> />
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
...@@ -59,6 +60,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => { ...@@ -59,6 +60,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
hash={ item.l1_tx_hash } hash={ item.l1_tx_hash }
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
truncation="constant_long"
/> />
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
...@@ -68,6 +70,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => { ...@@ -68,6 +70,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, implementation_name: '', ens_domain_name: null }} address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, implementation_name: '', ens_domain_name: null }}
isLoading={ isLoading } isLoading={ isLoading }
noCopy noCopy
truncation="constant"
/> />
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
...@@ -80,4 +83,4 @@ const DepositsListItem = ({ item, isLoading }: Props) => { ...@@ -80,4 +83,4 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
); );
}; };
export default DepositsListItem; export default OptimisticDepositsListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import DepositsTableItem from './DepositsTableItem'; import OptimisticDepositsTableItem from './OptimisticDepositsTableItem';
type Props = { type Props = {
items: Array<L2DepositsItem>; items: Array<OptimisticL2DepositsItem>;
top: number; top: number;
isLoading?: boolean; isLoading?: boolean;
} }
const DepositsTable = ({ items, top, isLoading }: Props) => { const OptimisticDepositsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px"> <Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }> <Thead top={ top }>
...@@ -28,11 +28,11 @@ const DepositsTable = ({ items, top, isLoading }: Props) => { ...@@ -28,11 +28,11 @@ const DepositsTable = ({ items, top, isLoading }: Props) => {
</Thead> </Thead>
<Tbody> <Tbody>
{ items.map((item, index) => ( { items.map((item, index) => (
<DepositsTableItem key={ item.l2_tx_hash + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/> <OptimisticDepositsTableItem key={ item.l2_tx_hash + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
); );
}; };
export default DepositsTable; export default OptimisticDepositsTable;
...@@ -2,7 +2,7 @@ import { Td, Tr, Skeleton } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { Td, Tr, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2';
import config from 'configs/app'; import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
...@@ -11,14 +11,14 @@ import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; ...@@ -11,14 +11,14 @@ import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
const feature = config.features.optimisticRollup; const rollupFeature = config.features.rollup;
type Props = { item: L2DepositsItem; isLoading?: boolean }; type Props = { item: OptimisticL2DepositsItem; isLoading?: boolean };
const WithdrawalsTableItem = ({ item, isLoading }: Props) => { const OptimisticDepositsTableItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.l1_block_timestamp).fromNow(); const timeAgo = dayjs(item.l1_block_timestamp).fromNow();
if (!feature.isEnabled) { if (!rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
return null; return null;
} }
...@@ -31,6 +31,7 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => { ...@@ -31,6 +31,7 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
fontWeight={ 600 } fontWeight={ 600 }
noIcon
/> />
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
...@@ -39,7 +40,8 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => { ...@@ -39,7 +40,8 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
hash={ item.l2_tx_hash } hash={ item.l2_tx_hash }
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
truncation="constant" truncation="constant_long"
noIcon
/> />
</Td> </Td>
<Td verticalAlign="middle" pr={ 12 }> <Td verticalAlign="middle" pr={ 12 }>
...@@ -49,7 +51,8 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => { ...@@ -49,7 +51,8 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
<TxEntityL1 <TxEntityL1
isLoading={ isLoading } isLoading={ isLoading }
hash={ item.l1_tx_hash } hash={ item.l1_tx_hash }
truncation="constant" truncation="constant_long"
noIcon
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
/> />
...@@ -71,4 +74,4 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => { ...@@ -71,4 +74,4 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
); );
}; };
export default WithdrawalsTableItem; export default OptimisticDepositsTableItem;
...@@ -24,7 +24,7 @@ const LatestBlocks = () => { ...@@ -24,7 +24,7 @@ const LatestBlocks = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
// const blocksMaxCount = isMobile ? 2 : 3; // const blocksMaxCount = isMobile ? 2 : 3;
let blocksMaxCount: number; let blocksMaxCount: number;
if (config.features.optimisticRollup.isEnabled || config.UI.views.block.hiddenFields?.total_reward) { if (config.features.rollup.isEnabled || config.UI.views.block.hiddenFields?.total_reward) {
blocksMaxCount = isMobile ? 4 : 5; blocksMaxCount = isMobile ? 4 : 5;
} else { } else {
blocksMaxCount = isMobile ? 2 : 3; blocksMaxCount = isMobile ? 2 : 3;
......
...@@ -59,14 +59,14 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => { ...@@ -59,14 +59,14 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => {
<Skeleton isLoaded={ !isLoading }>Txn</Skeleton> <Skeleton isLoaded={ !isLoading }>Txn</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ block.tx_count }</span></Skeleton> <Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ block.tx_count }</span></Skeleton>
{ !config.features.optimisticRollup.isEnabled && !config.UI.views.block.hiddenFields?.total_reward && ( { !config.features.rollup.isEnabled && !config.UI.views.block.hiddenFields?.total_reward && (
<> <>
<Skeleton isLoaded={ !isLoading }>Reward</Skeleton> <Skeleton isLoaded={ !isLoading }>Reward</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ totalReward.dp(10).toFixed() }</span></Skeleton> <Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ totalReward.dp(10).toFixed() }</span></Skeleton>
</> </>
) } ) }
{ !config.features.optimisticRollup.isEnabled && !config.UI.views.block.hiddenFields?.miner && ( { !config.features.rollup.isEnabled && !config.UI.views.block.hiddenFields?.miner && (
<> <>
<Skeleton isLoaded={ !isLoading } textTransform="capitalize">{ getNetworkValidatorTitle() }</Skeleton> <Skeleton isLoaded={ !isLoading } textTransform="capitalize">{ getNetworkValidatorTitle() }</Skeleton>
<AddressEntity <AddressEntity
...@@ -74,6 +74,7 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => { ...@@ -74,6 +74,7 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => {
isLoading={ isLoading } isLoading={ isLoading }
noIcon noIcon
noCopy noCopy
truncation="constant"
/> />
</> </>
) } ) }
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2';
import config from 'configs/app'; import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
...@@ -15,18 +15,18 @@ import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; ...@@ -15,18 +15,18 @@ import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
const feature = config.features.optimisticRollup; const feature = config.features.rollup;
type Props = { type Props = {
item: L2DepositsItem; item: OptimisticL2DepositsItem;
isLoading?: boolean; isLoading?: boolean;
} }
const LatestTxsItem = ({ item, isLoading }: Props) => { const LatestDepositsItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.l1_block_timestamp).fromNow(); const timeAgo = dayjs(item.l1_block_timestamp).fromNow();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
if (!feature.isEnabled) { if (!feature.isEnabled || feature.type !== 'optimistic') {
return null; return null;
} }
...@@ -46,6 +46,7 @@ const LatestTxsItem = ({ item, isLoading }: Props) => { ...@@ -46,6 +46,7 @@ const LatestTxsItem = ({ item, isLoading }: Props) => {
hash={ item.l1_tx_hash } hash={ item.l1_tx_hash }
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
truncation={ isMobile ? 'constant_long' : 'dynamic' }
/> />
); );
...@@ -55,6 +56,7 @@ const LatestTxsItem = ({ item, isLoading }: Props) => { ...@@ -55,6 +56,7 @@ const LatestTxsItem = ({ item, isLoading }: Props) => {
hash={ item.l2_tx_hash } hash={ item.l2_tx_hash }
fontSize="sm" fontSize="sm"
lineHeight={ 5 } lineHeight={ 5 }
truncation={ isMobile ? 'constant_long' : 'dynamic' }
/> />
); );
...@@ -116,4 +118,4 @@ const LatestTxsItem = ({ item, isLoading }: Props) => { ...@@ -116,4 +118,4 @@ const LatestTxsItem = ({ item, isLoading }: Props) => {
); );
}; };
export default React.memo(LatestTxsItem); export default React.memo(LatestDepositsItem);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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