Commit cf6fb442 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into tom2drum/issue-1578

parents 960e80d9 98d3bbcd
...@@ -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,16 @@ jobs: ...@@ -156,8 +203,16 @@ 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
continue-on-error: true
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';
......
import type { Feature } from './types';
import { GAS_UNITS } from 'types/client/gasTracker';
import type { GasUnit } from 'types/client/gasTracker';
import { getEnvValue, parseEnvJson } from '../utils';
const isDisabled = getEnvValue('NEXT_PUBLIC_GAS_TRACKER_ENABLED') === 'false';
const units = ((): Array<GasUnit> => {
const envValue = getEnvValue('NEXT_PUBLIC_GAS_TRACKER_UNITS');
if (!envValue) {
return [ 'usd', 'gwei' ];
}
const units = parseEnvJson<Array<GasUnit>>(envValue)?.filter((type) => GAS_UNITS.includes(type)) || [];
return units;
})();
const title = 'Gas tracker';
const config: Feature<{ units: Array<GasUnit> }> = (() => {
if (!isDisabled && units.length > 0) {
return Object.freeze({
title,
isEnabled: true,
units,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -6,6 +6,7 @@ export { default as beaconChain } from './beaconChain'; ...@@ -6,6 +6,7 @@ export { default as beaconChain } from './beaconChain';
export { default as bridgedTokens } from './bridgedTokens'; export { default as bridgedTokens } from './bridgedTokens';
export { default as blockchainInteraction } from './blockchainInteraction'; export { default as blockchainInteraction } from './blockchainInteraction';
export { default as csvExport } from './csvExport'; export { default as csvExport } from './csvExport';
export { default as gasTracker } from './gasTracker';
export { default as googleAnalytics } from './googleAnalytics'; export { default as googleAnalytics } from './googleAnalytics';
export { default as graphqlApiDocs } from './graphqlApiDocs'; export { default as graphqlApiDocs } from './graphqlApiDocs';
export { default as growthBook } from './growthBook'; export { default as growthBook } from './growthBook';
...@@ -13,14 +14,15 @@ export { default as marketplace } from './marketplace'; ...@@ -13,14 +14,15 @@ 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';
export { default as stats } from './stats'; export { default as stats } from './stats';
export { default as suave } from './suave'; export { default as suave } from './suave';
export { default as swapButton } from './swapButton';
export { default as txInterpretation } from './txInterpretation'; export { default as txInterpretation } from './txInterpretation';
export { default as userOps } from './userOps'; export { default as userOps } from './userOps';
export { default as validators } from './validators';
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';
...@@ -4,25 +4,43 @@ import chain from '../chain'; ...@@ -4,25 +4,43 @@ import chain from '../chain';
import { getEnvValue, getExternalAssetFilePath } from '../utils'; import { getEnvValue, getExternalAssetFilePath } from '../utils';
// config file will be downloaded at run-time and saved in the public folder // config file will be downloaded at run-time and saved in the public folder
const enabled = getEnvValue('NEXT_PUBLIC_MARKETPLACE_ENABLED');
const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'); const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL');
const submitFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM'); const submitFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM');
const suggestIdeasFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM');
const categoriesUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL'); const categoriesUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL');
const adminServiceApiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST');
const title = 'Marketplace'; const title = 'Marketplace';
const config: Feature<{ configUrl: string; submitFormUrl: string; categoriesUrl: string | undefined }> = (() => { const config: Feature<(
if ( { configUrl: string } |
chain.rpcUrl && { api: { endpoint: string; basePath: string } }
configUrl && ) & { submitFormUrl: string; categoriesUrl: string | undefined; suggestIdeasFormUrl: string | undefined }
submitFormUrl > = (() => {
) { if (enabled === 'true' && chain.rpcUrl && submitFormUrl) {
return Object.freeze({ if (configUrl) {
title, return Object.freeze({
isEnabled: true, title,
configUrl, isEnabled: true,
submitFormUrl, configUrl,
categoriesUrl, submitFormUrl,
}); categoriesUrl,
suggestIdeasFormUrl,
});
} else if (adminServiceApiHost) {
return Object.freeze({
title,
isEnabled: true,
submitFormUrl,
categoriesUrl,
suggestIdeasFormUrl,
api: {
endpoint: adminServiceApiHost,
basePath: '',
},
});
}
} }
return Object.freeze({ return Object.freeze({
......
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';
import marketplace from './marketplace';
const value = getEnvValue('NEXT_PUBLIC_SWAP_BUTTON_URL');
const title = 'Swap button';
function isValidUrl(string: string) {
try {
new URL(string);
return true;
} catch (error) {
return false;
}
}
const config: Feature<{ dappId: string } | { url: string }> = (() => {
if (value) {
if (isValidUrl(value)) {
return Object.freeze({
title,
isEnabled: true,
url: value,
});
} else if (marketplace.isEnabled) {
return Object.freeze({
title,
isEnabled: true,
dappId: value,
});
}
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
import type { Feature } from './types'; import type { Feature } from './types';
import { VALIDATORS_CHAIN_TYPE } from 'types/client/validators';
import type { ValidatorsChainType } from 'types/client/validators';
import { getEnvValue } from '../utils'; import { getEnvValue } from '../utils';
const title = 'ZkEVM rollup (L2) chain'; const chainType = ((): ValidatorsChainType | undefined => {
const envValue = getEnvValue('NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE') as ValidatorsChainType | undefined;
return envValue && VALIDATORS_CHAIN_TYPE.includes(envValue) ? envValue : undefined;
})();
const config: Feature<{ L1BaseUrl: string; withdrawalUrl?: string }> = (() => { const title = 'Validators list';
const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL');
const isZkEvm = getEnvValue('NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK') === 'true';
if (isZkEvm && L1BaseUrl) { const config: Feature<{ chainType: ValidatorsChainType }> = (() => {
if (chainType) {
return Object.freeze({ return Object.freeze({
title, title,
isEnabled: true, isEnabled: true,
L1BaseUrl, chainType,
}); });
} }
......
...@@ -49,7 +49,6 @@ const UI = Object.freeze({ ...@@ -49,7 +49,6 @@ const UI = Object.freeze({
background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND') || HOMEPAGE_PLATE_BACKGROUND_DEFAULT, background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND') || HOMEPAGE_PLATE_BACKGROUND_DEFAULT,
textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR') || 'white', textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR') || 'white',
}, },
showGasTracker: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER') === 'false' ? false : true,
showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true, showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true,
}, },
views, views,
...@@ -70,6 +69,7 @@ const UI = Object.freeze({ ...@@ -70,6 +69,7 @@ const UI = Object.freeze({
ides: { ides: {
items: parseEnvJson<Array<ContractCodeIde>>(getEnvValue('NEXT_PUBLIC_CONTRACT_CODE_IDES')) || [], items: parseEnvJson<Array<ContractCodeIde>>(getEnvValue('NEXT_PUBLIC_CONTRACT_CODE_IDES')) || [],
}, },
hasContractAuditReports: getEnvValue('NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS') === 'true' ? true : false,
}); });
export default UI; export default UI;
...@@ -38,11 +38,12 @@ NEXT_PUBLIC_HAS_BEACON_CHAIN=true ...@@ -38,11 +38,12 @@ NEXT_PUBLIC_HAS_BEACON_CHAIN=true
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout 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_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.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_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
...@@ -33,6 +33,7 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c ...@@ -33,6 +33,7 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc ## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true
# app features # app features
NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_APP_ENV=development
...@@ -54,4 +55,4 @@ NEXT_PUBLIC_HAS_USER_OPS=true ...@@ -54,4 +55,4 @@ NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
#meta #meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true
\ No newline at end of file
...@@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ ...@@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/
## homepage ## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND= NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=
## sidebar ## sidebar
NEXT_PUBLIC_NETWORK_LOGO= NEXT_PUBLIC_NETWORK_LOGO=
...@@ -44,8 +43,6 @@ NEXT_PUBLIC_APP_INSTANCE=jest ...@@ -44,8 +43,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
......
...@@ -45,10 +45,13 @@ NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c18099 ...@@ -45,10 +45,13 @@ NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c18099
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.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_SWAP_BUTTON_URL=uniswap
...@@ -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/
...@@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ ...@@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/
## homepage ## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
## sidebar ## sidebar
## footer ## footer
NEXT_PUBLIC_GIT_TAG=v1.0.11 NEXT_PUBLIC_GIT_TAG=v1.0.11
...@@ -39,10 +38,9 @@ NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE= ...@@ -39,10 +38,9 @@ NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=
# app features # app features
NEXT_PUBLIC_APP_ENV=testing NEXT_PUBLIC_APP_ENV=testing
NEXT_PUBLIC_APP_INSTANCE=pw NEXT_PUBLIC_APP_INSTANCE=pw
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
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'),
];
const VISITED = {};
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,
visited: VISITED,
});
}
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.
...@@ -41,7 +41,9 @@ async function validateEnvs(appEnvs: Record<string, string>) { ...@@ -41,7 +41,9 @@ async function validateEnvs(appEnvs: Record<string, string>) {
]; ];
for await (const envName of envsWithJsonConfig) { for await (const envName of envsWithJsonConfig) {
appEnvs[envName] = await(appEnvs[envName] ? getExternalJsonContent(envName) : Promise.resolve()) || '[]'; if (appEnvs[envName]) {
appEnvs[envName] = await getExternalJsonContent(envName) || '[]';
}
} }
await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false });
......
...@@ -12,11 +12,16 @@ import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; ...@@ -12,11 +12,16 @@ import type { AdButlerConfig } from '../../../types/client/adButlerConfig';
import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders'; import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders';
import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders'; import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders';
import type { ContractCodeIde } from '../../../types/client/contract'; import type { ContractCodeIde } from '../../../types/client/contract';
import { GAS_UNITS } from '../../../types/client/gasTracker';
import type { GasUnit } from '../../../types/client/gasTracker';
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 { VALIDATORS_CHAIN_TYPE } from '../../../types/client/validators';
import type { ValidatorsChainType } from '../../../types/client/validators';
import type { WalletType } from '../../../types/client/wallets'; import type { WalletType } from '../../../types/client/wallets';
import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
...@@ -78,20 +83,42 @@ const marketplaceAppSchema: yup.ObjectSchema<MarketplaceAppOverview> = yup ...@@ -78,20 +83,42 @@ const marketplaceAppSchema: yup.ObjectSchema<MarketplaceAppOverview> = yup
const marketplaceSchema = yup const marketplaceSchema = yup
.object() .object()
.shape({ .shape({
NEXT_PUBLIC_MARKETPLACE_ENABLED: yup.boolean(),
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: yup NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: yup
.array() .array()
.json() .json()
.of(marketplaceAppSchema), .of(marketplaceAppSchema)
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: true,
then: (schema) => schema,
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: yup NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: yup
.array() .array()
.json() .json()
.of(yup.string()), .of(yup.string())
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: true,
then: (schema) => schema,
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: yup NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: yup
.string() .string()
.when('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', { .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: (value: Array<unknown>) => value.length > 0, is: true,
then: (schema) => schema.test(urlTest).required(), then: (schema) => schema.test(urlTest).required(),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'), // eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: yup
.string()
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: true,
then: (schema) => schema.test(urlTest),
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}), }),
}); });
...@@ -114,23 +141,20 @@ const beaconChainSchema = yup ...@@ -114,23 +141,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"'),
}), }),
}); });
...@@ -368,7 +392,6 @@ const schema = yup ...@@ -368,7 +392,6 @@ const schema = yup
.of(yup.string<ChainIndicatorId>().oneOf([ 'daily_txs', 'coin_price', 'market_cap', 'tvl' ])), .of(yup.string<ChainIndicatorId>().oneOf([ 'daily_txs', 'coin_price', 'market_cap', 'tvl' ])),
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(), NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(),
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(), NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(),
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER: yup.boolean(),
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME: yup.boolean(), NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME: yup.boolean(),
// b. sidebar // b. sidebar
...@@ -437,6 +460,7 @@ const schema = yup ...@@ -437,6 +460,7 @@ const schema = yup
.transform(replaceQuotes) .transform(replaceQuotes)
.json() .json()
.of(contractCodeIdeSchema), .of(contractCodeIdeSchema),
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: yup.boolean(),
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS: yup.boolean(),
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(),
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(),
...@@ -468,6 +492,10 @@ const schema = yup ...@@ -468,6 +492,10 @@ const schema = yup
NEXT_PUBLIC_OG_IMAGE_URL: yup.string().test(urlTest), NEXT_PUBLIC_OG_IMAGE_URL: yup.string().test(urlTest),
NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(), NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(),
NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(), NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(),
NEXT_PUBLIC_SWAP_BUTTON_URL: yup.string(),
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE: yup.string<ValidatorsChainType>().oneOf(VALIDATORS_CHAIN_TYPE),
NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(),
NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string<GasUnit>().oneOf(GAS_UNITS)),
// 6. External services envs // 6. External services envs
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),
......
...@@ -19,12 +19,10 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false ...@@ -19,12 +19,10 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='#fff' NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='#fff'
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgb(255, 145, 0)' NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgb(255, 145, 0)'
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_GAS_TRACKER_ENABLED=true
NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei']
NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='<a href="#">Hello</a>' NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='<a href="#">Hello</a>'
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
...@@ -53,3 +51,5 @@ NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee', ...@@ -53,3 +51,5 @@ NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee',
NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false
NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket'] NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket']
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability
\ No newline at end of file
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://example.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com
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
...@@ -179,7 +179,9 @@ frontend: ...@@ -179,7 +179,9 @@ frontend:
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-goerli.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-goerli.json
NEXT_PUBLIC_MARKETPLACE_ENABLED: true
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout 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_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
...@@ -191,10 +193,11 @@ frontend: ...@@ -191,10 +193,11 @@ 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
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
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
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
......
...@@ -148,7 +148,9 @@ frontend: ...@@ -148,7 +148,9 @@ frontend:
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation
NEXT_PUBLIC_MARKETPLACE_ENABLED: true
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_ENV: development
NEXT_PUBLIC_APP_INSTANCE: main NEXT_PUBLIC_APP_INSTANCE: main
NEXT_PUBLIC_STATS_API_HOST: https://stats-test.k8s-dev.blockscout.com/ NEXT_PUBLIC_STATS_API_HOST: https://stats-test.k8s-dev.blockscout.com/
...@@ -165,6 +167,7 @@ frontend: ...@@ -165,6 +167,7 @@ frontend:
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -52,7 +52,9 @@ frontend: ...@@ -52,7 +52,9 @@ frontend:
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/base-goerli.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/base-goerli.json
NEXT_PUBLIC_API_HOST: blockscout-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_API_HOST: blockscout-optimism-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_MARKETPLACE_ENABLED: true
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
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_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
...@@ -65,11 +67,12 @@ frontend: ...@@ -65,11 +67,12 @@ 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
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -58,12 +58,13 @@ frontend: ...@@ -58,12 +58,13 @@ frontend:
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_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_AUTH_URL: https://blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_AUTH_URL: https://blockscout-main.k8s-dev.blockscout.com
NEXT_PUBLIC_MARKETPLACE_ENABLED: true
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]"
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
...@@ -76,6 +77,8 @@ frontend: ...@@ -76,6 +77,8 @@ frontend:
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_HAS_USER_OPS: true NEXT_PUBLIC_HAS_USER_OPS: true
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
...@@ -86,3 +89,4 @@ frontend: ...@@ -86,3 +89,4 @@ frontend:
FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY
NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN
...@@ -57,7 +57,7 @@ B. Pre-defined configuration: ...@@ -57,7 +57,7 @@ B. Pre-defined configuration:
1. Optionally, clone `.env.example` file into `configs/envs/.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need. 1. Optionally, clone `.env.example` file into `configs/envs/.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need.
2. Choose one of the predefined configurations located in the `/configs/envs` folder. 2. Choose one of the predefined configurations located in the `/configs/envs` folder.
3. Start your local dev server using the `yarn dev:<config_name>` command. 3. Start your local dev server using the `yarn dev:preset <config_preset_name>` command.
4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`). 4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`).
...@@ -79,18 +79,21 @@ These are the steps that you have to follow to make everything work: ...@@ -79,18 +79,21 @@ These are the steps that you have to follow to make everything work:
2. Make sure that you have added a property to React app config (`configs/app/index.ts`) in appropriate section that is associated with this variable; do not use ENV variable values directly in the application code; decide where this variable belongs to and place it under the certain section: 2. Make sure that you have added a property to React app config (`configs/app/index.ts`) in appropriate section that is associated with this variable; do not use ENV variable values directly in the application code; decide where this variable belongs to and place it under the certain section:
- `app` - the front-end app itself - `app` - the front-end app itself
- `api` - the main API configuration - `api` - the main API configuration
- `chain` - the Blockchain parameters
- `UI` - the app UI customization - `UI` - the app UI customization
- `meta` - SEO and meta-tags customization
- `features` - the particular feature of the app - `features` - the particular feature of the app
- `services` - some 3rd party service integration which is not related to one particular feature - `services` - some 3rd party service integration which is not related to one particular feature
3. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `configs/envs` where it is needed 3. If a new variable is meant to store the URL of an external API service, remember to include its value in the Content-Security-Policy document header. Refer to `nextjs/csp/policies/app.ts` for details.
4. Add the variable to CI configs where it is needed 4. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `configs/envs` where it is needed
5. Add the variable to CI configs where it is needed
- `deploy/values/review/values.yaml.gotmpl` - review development environment - `deploy/values/review/values.yaml.gotmpl` - review development environment
- `deploy/values/main/values.yaml` - main development environment - `deploy/values/main/values.yaml` - main development environment
- `deploy/values/review-l2/values.yaml.gotmpl` - review development environment for L2 networks - `deploy/values/review-l2/values.yaml.gotmpl` - review development environment for L2 networks
- `deploy/values/l2-optimism-goerli/values.yaml` - main development environment - `deploy/values/l2-optimism-goerli/values.yaml` - main development environment
5. If your variable is meant to receive a link to some external resource (image or JSON-config file), extend the array `ASSETS_ENVS` in `deploy/scripts/download_assets.sh` with your variable name 6. If your variable is meant to receive a link to some external resource (image or JSON-config file), extend the array `ASSETS_ENVS` in `deploy/scripts/download_assets.sh` with your variable name
6. Add validation schema for the new variable into the file `deploy/tools/envs-validator/schema.ts` 7. Add validation schema for the new variable into the file `deploy/tools/envs-validator/schema.ts`
7. Check if modified validation schema is valid by doing the following steps: 8. Check if modified validation schema is valid by doing the following steps:
- change your current directory to `deploy/tools/envs-validator` - change your current directory to `deploy/tools/envs-validator`
- install deps with `yarn` command - install deps with `yarn` command
- add your variable into `./test/.env.base` test preset or create a new test preset if needed - add your variable into `./test/.env.base` test preset or create a new test preset if needed
...@@ -98,7 +101,7 @@ These are the steps that you have to follow to make everything work: ...@@ -98,7 +101,7 @@ These are the steps that you have to follow to make everything work:
- add example of file content into `./test/assets` directory; the file name should be constructed by stripping away prefix `NEXT_PUBLIC_` and postfix `_URL` if any, and converting the remaining string to lowercase (for example, `NEXT_PUBLIC_MARKETPLACE_CONFIG_URL` will become `marketplace_config.json`) - add example of file content into `./test/assets` directory; the file name should be constructed by stripping away prefix `NEXT_PUBLIC_` and postfix `_URL` if any, and converting the remaining string to lowercase (for example, `NEXT_PUBLIC_MARKETPLACE_CONFIG_URL` will become `marketplace_config.json`)
- in the main script `index.ts` extend array `envsWithJsonConfig` with your variable name - in the main script `index.ts` extend array `envsWithJsonConfig` with your variable name
- run `yarn test` command to see the validation result - run `yarn test` command to see the validation result
8. Don't forget to mention in the PR notes that new ENV variable was added 9. Don't forget to mention in the PR notes that new ENV variable was added
&nbsp; &nbsp;
......
# 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 |
| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | v1.25.0 | Replaced by NEXT_PUBLIC_GAS_TRACKER_ENABLED |
\ No newline at end of file
This diff is collapsed.
<svg viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.121 1C3.131 1 .667 3.464.667 6.455V41H26.12V28.273h3.637v7.272c0 2.99 2.464 5.455 5.454 5.455 2.99 0 5.455-2.465 5.455-5.455V17.023a5.33 5.33 0 0 0-1.591-3.807l-8.58-8.58-2.557 2.557 5.17 5.17c-1.952.832-3.352 2.756-3.352 5 0 2.99 2.465 5.455 5.455 5.455.64 0 1.243-.135 1.818-.34v13.067c0 1.03-.788 1.819-1.818 1.819a1.786 1.786 0 0 1-1.818-1.819v-7.272c0-1.989-1.648-3.637-3.636-3.637H26.12V6.455c0-2.99-2.464-5.455-5.454-5.455H6.12Zm0 3.636h14.546c1.03 0 1.818.789 1.818 1.819v7.272H4.303V6.455c0-1.03.788-1.819 1.818-1.819Zm29.091 10.91c1.023 0 1.818.795 1.818 1.818a1.795 1.795 0 0 1-1.818 1.818 1.795 1.795 0 0 1-1.818-1.818c0-1.023.795-1.819 1.818-1.819ZM4.303 17.364h18.182v20H4.303v-20Z" fill="currentColor" stroke="currentColor"/>
</svg>
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.05 8.552c3.799-3.798 7.474-4.099 9.352-3.954.145 1.878-.156 5.553-3.954 9.352l-11.32 11.32-5.398-5.398 9.163-9.162.008-.01 2.15-2.148ZM22.817 5.32l-1.484 1.483H9.94a3.81 3.81 0 0 0-2.656 1.132l-6.166 6.147-.003.003a3.81 3.81 0 0 0 1.95 6.43h.002l7.302 1.464 7.653 7.653 1.463 7.301v.003a3.812 3.812 0 0 0 6.43 1.95l6.151-6.169a3.81 3.81 0 0 0 1.132-2.656V18.667l1.483-1.484C40.216 11.65 40.24 5.961 39.863 3.36A3.773 3.773 0 0 0 36.641.137c-2.602-.376-8.29-.353-13.824 5.182ZM5.358 16.31l5.388 1.08 6.015-6.015h-6.452l-4.95 4.935ZM22.61 29.255l1.08 5.387 4.935-4.951v-6.452l-6.015 6.015Zm-13.608-.278a2.286 2.286 0 1 0-2.29-3.958c-3.224 1.866-4.709 5.062-5.404 7.487a19.116 19.116 0 0 0-.697 4.184 11.72 11.72 0 0 0-.012.38v.044l2.286.001H.598a2.286 2.286 0 0 0 2.287 2.287v-2.287l.001 2.287h.045a9.419 9.419 0 0 0 .38-.013 19.11 19.11 0 0 0 4.184-.698c2.424-.694 5.62-2.179 7.486-5.404a2.286 2.286 0 0 0-3.958-2.29c-1.012 1.749-2.874 2.75-4.788 3.299-.244.07-.483.13-.714.183.053-.23.113-.47.183-.714.549-1.914 1.55-3.776 3.298-4.788Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.98 5.36c-.387 0-.645.258-.645.645a4.485 4.485 0 0 1-4.512 4.512h-.451c-.13 0-.194 0-.322-.064-.13 0-.194-.065-.323-.065-.064 0-.129-.064-.193-.064-.451-.129-.903-.387-1.29-.645-.064-.064-.128-.064-.128-.129-.065-.064-.13-.129-.194-.129-.129-.064-.258-.193-.322-.257l-.129-.13 1.16-.193a.669.669 0 0 0 .516-.773.669.669 0 0 0-.773-.516l-2.128.387-.515.129a.435.435 0 0 0-.387.258 1.195 1.195 0 0 0-.13.45l.517 2.708c.064.323.322.516.644.516h.13a.669.669 0 0 0 .515-.774l-.194-1.095c.13.128.323.257.452.386h.064c.065.065.129.13.193.13.13.128.258.193.452.257.129.258.322.322.515.451h.065c.064.065.193.065.322.13.258.064.516.128.71.193.128 0 .257.064.386.064h.774a5.778 5.778 0 0 0 5.801-5.801c.065-.323-.258-.58-.58-.58ZM.73 6.65c.387 0 .645-.258.645-.645a4.485 4.485 0 0 1 4.513-4.512h.45c.13 0 .194 0 .323.064.13 0 .194.065.323.065.064 0 .128.064.193.064.451.129.902.322 1.29.645.064.064.128.064.193.129.064.064.129.064.193.128.064.065.129.194.258.258l.129.13-1.16.193a.669.669 0 0 0-.516.773c.064.322.322.516.644.516h.13l2.707-.516a.669.669 0 0 0 .515-.773L11.045.526A.669.669 0 0 0 10.27.01a.669.669 0 0 0-.516.773L9.95 1.88c-.13-.129-.322-.322-.451-.386h-.065c-.064-.065-.129-.13-.193-.13-.13-.128-.258-.193-.451-.322C8.596.913 8.403.849 8.21.72h-.065C8.079.655 7.95.655 7.822.59a10.574 10.574 0 0 1-.71-.193c-.128 0-.257-.065-.386-.065h-.773A5.778 5.778 0 0 0 .15 6.134c-.064.258.194.516.58.516Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.671 22.827a4.363 4.363 0 0 0 1.53-2.195 4.338 4.338 0 0 0-.042-2.67 4.366 4.366 0 0 0-1.6-2.145 4.41 4.41 0 0 0-5.117 0 4.366 4.366 0 0 0-1.6 2.145 4.338 4.338 0 0 0-.044 2.67c.267.873.802 1.64 1.53 2.195a6.578 6.578 0 0 0-3.256 3.127.705.705 0 0 0 .046.7.712.712 0 0 0 .613.346h1.246c.461-1.593 2.088-2.77 4.023-2.77 1.936 0 3.562 1.177 4.023 2.77h1.247a.716.716 0 0 0 .612-.346.705.705 0 0 0 .046-.7 6.578 6.578 0 0 0-3.257-3.127Zm-.179-3.489a2.492 2.492 0 1 1-4.984 0 2.492 2.492 0 0 1 4.984 0ZM15.002 3a1 1 0 0 1 .832.445l2.063 3.094 2.482-1.986a1 1 0 0 1 1.605.977l-1.333 6.669a1 1 0 0 1-.98.804h-9.337a1 1 0 0 1-.98-.804l-1.335-6.67a1 1 0 0 1 1.606-.976l2.482 1.986 2.063-3.094A1 1 0 0 1 15.002 3Zm0 2.803-1.836 2.753a1 1 0 0 1-1.456.226l-1.191-.952.635 3.173h7.696l.635-3.173-1.19.952a1 1 0 0 1-1.458-.226l-1.835-2.753Z" fill="currentColor"/>
</svg>
This diff is collapsed.
...@@ -41,6 +41,7 @@ export default function useApiFetch() { ...@@ -41,6 +41,7 @@ export default function useApiFetch() {
'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined, 'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined,
Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined, Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined,
'x-csrf-token': withBody && csrfToken ? csrfToken : undefined, 'x-csrf-token': withBody && csrfToken ? csrfToken : undefined,
...resource.headers,
...fetchParams?.headers, ...fetchParams?.headers,
}, Boolean) as HeadersInit; }, Boolean) as HeadersInit;
......
...@@ -7,7 +7,6 @@ import { STORAGE_KEY, STORAGE_LIMIT } from './consts'; ...@@ -7,7 +7,6 @@ import { STORAGE_KEY, STORAGE_LIMIT } from './consts';
export interface GrowthBookFeatures { export interface GrowthBookFeatures {
test_value: string; test_value: string;
marketplace_exp: boolean;
} }
export const growthBook = (() => { export const growthBook = (() => {
......
...@@ -66,8 +66,16 @@ export default function useNavItems(): ReturnType { ...@@ -66,8 +66,16 @@ export default function useNavItems(): ReturnType {
icon: 'ENS', icon: 'ENS',
isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]', isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]',
} : null; } : null;
const validators = config.features.validators.isEnabled ? {
text: 'Top validators',
nextRoute: { pathname: '/validators' as const },
icon: 'validator',
isActive: pathname === '/validators',
} : null;
const rollupFeature = config.features.rollup;
if (config.features.zkEvmRollup.isEnabled) { if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') {
blockchainNavItems = [ blockchainNavItems = [
[ [
txs, txs,
...@@ -82,11 +90,12 @@ export default function useNavItems(): ReturnType { ...@@ -82,11 +90,12 @@ export default function useNavItems(): ReturnType {
].filter(Boolean), ].filter(Boolean),
[ [
topAccounts, topAccounts,
validators,
verifiedContracts, verifiedContracts,
ensLookup, ensLookup,
].filter(Boolean), ].filter(Boolean),
]; ];
} else if (config.features.optimisticRollup.isEnabled) { } else if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
blockchainNavItems = [ blockchainNavItems = [
[ [
txs, txs,
...@@ -103,6 +112,24 @@ export default function useNavItems(): ReturnType { ...@@ -103,6 +112,24 @@ export default function useNavItems(): ReturnType {
{ text: 'Output roots', nextRoute: { pathname: '/output-roots' as const }, icon: 'output_roots', isActive: pathname === '/output-roots' }, { text: 'Output roots', nextRoute: { pathname: '/output-roots' as const }, icon: 'output_roots', isActive: pathname === '/output-roots' },
], ],
[ [
userOps,
topAccounts,
validators,
verifiedContracts,
ensLookup,
].filter(Boolean),
];
} else if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
blockchainNavItems = [
[
txs,
// eslint-disable-next-line max-len
{ text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/deposits' },
// eslint-disable-next-line max-len
{ text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' },
],
[
blocks,
userOps, userOps,
topAccounts, topAccounts,
verifiedContracts, verifiedContracts,
...@@ -115,6 +142,7 @@ export default function useNavItems(): ReturnType { ...@@ -115,6 +142,7 @@ export default function useNavItems(): ReturnType {
userOps, userOps,
blocks, blocks,
topAccounts, topAccounts,
validators,
verifiedContracts, verifiedContracts,
ensLookup, ensLookup,
config.features.beaconChain.isEnabled && { config.features.beaconChain.isEnabled && {
...@@ -165,7 +193,7 @@ export default function useNavItems(): ReturnType { ...@@ -165,7 +193,7 @@ export default function useNavItems(): ReturnType {
isActive: pathname.startsWith('/token'), isActive: pathname.startsWith('/token'),
}, },
config.features.marketplace.isEnabled ? { config.features.marketplace.isEnabled ? {
text: 'Apps', text: 'DApps',
nextRoute: { pathname: '/apps' as const }, nextRoute: { pathname: '/apps' as const },
icon: 'apps', icon: 'apps',
isActive: pathname.startsWith('/app'), isActive: pathname.startsWith('/app'),
...@@ -191,8 +219,13 @@ export default function useNavItems(): ReturnType { ...@@ -191,8 +219,13 @@ export default function useNavItems(): ReturnType {
nextRoute: { pathname: '/contract-verification' as const }, nextRoute: { pathname: '/contract-verification' as const },
isActive: pathname.startsWith('/contract-verification'), isActive: pathname.startsWith('/contract-verification'),
}, },
config.features.gasTracker.isEnabled && {
text: 'Gas tracker',
nextRoute: { pathname: '/gas-tracker' as const },
isActive: pathname.startsWith('/gas-tracker'),
},
...config.UI.sidebar.otherLinks, ...config.UI.sidebar.otherLinks,
], ].filter(Boolean),
}, },
].filter(Boolean); ].filter(Boolean);
......
...@@ -42,6 +42,8 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -42,6 +42,8 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/404': 'Regular page', '/404': 'Regular page',
'/name-domains': 'Root page', '/name-domains': 'Root page',
'/name-domains/[name]': 'Regular page', '/name-domains/[name]': 'Regular page',
'/validators': 'Root page',
'/gas-tracker': 'Root page',
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': 'Regular page', '/login': 'Regular page',
......
...@@ -45,6 +45,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -45,6 +45,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/404': DEFAULT_TEMPLATE, '/404': DEFAULT_TEMPLATE,
'/name-domains': DEFAULT_TEMPLATE, '/name-domains': DEFAULT_TEMPLATE,
'/name-domains/[name]': DEFAULT_TEMPLATE, '/name-domains/[name]': DEFAULT_TEMPLATE,
'/validators': DEFAULT_TEMPLATE,
'/gas-tracker': DEFAULT_TEMPLATE,
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE, '/login': DEFAULT_TEMPLATE,
......
...@@ -40,6 +40,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -40,6 +40,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/404': 'error - page not found', '/404': 'error - page not found',
'/name-domains': 'domains search and resolve', '/name-domains': 'domains search and resolve',
'/name-domains/[name]': '%name% domain details', '/name-domains/[name]': '%name% domain details',
'/validators': 'validators list',
'/gas-tracker': 'gas tracker',
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': 'login', '/login': 'login',
......
...@@ -15,8 +15,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -15,8 +15,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/tokens': 'Tokens', '/tokens': 'Tokens',
'/token/[hash]': 'Token details', '/token/[hash]': 'Token details',
'/token/[hash]/instance/[id]': 'Token Instance', '/token/[hash]/instance/[id]': 'Token Instance',
'/apps': 'Apps', '/apps': 'DApps',
'/apps/[id]': 'App', '/apps/[id]': 'DApp',
'/stats': 'Stats', '/stats': 'Stats',
'/api-docs': 'REST API', '/api-docs': 'REST API',
'/graphiql': 'GraphQL', '/graphiql': 'GraphQL',
...@@ -40,6 +40,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -40,6 +40,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/404': '404', '/404': '404',
'/name-domains': 'Domains search and resolve', '/name-domains': 'Domains search and resolve',
'/name-domains/[name]': 'Domain details', '/name-domains/[name]': 'Domain details',
'/validators': 'Validators list',
'/gas-tracker': 'Gas tracker',
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': 'Login', '/login': 'Login',
......
...@@ -70,7 +70,7 @@ Type extends EventTypes.VERIFY_TOKEN ? { ...@@ -70,7 +70,7 @@ Type extends EventTypes.VERIFY_TOKEN ? {
'Action': 'Form opened' | 'Submit'; 'Action': 'Form opened' | 'Submit';
} : } :
Type extends EventTypes.WALLET_CONNECT ? { Type extends EventTypes.WALLET_CONNECT ? {
'Source': 'Header' | 'Smart contracts'; 'Source': 'Header' | 'Smart contracts' | 'Swap button';
'Status': 'Started' | 'Connected'; 'Status': 'Started' | 'Connected';
} : } :
Type extends EventTypes.WALLET_ACTION ? { Type extends EventTypes.WALLET_ACTION ? {
......
import type { NextRouter } from 'next/router';
export default function removeQueryParam(router: NextRouter, param: string) {
const { pathname, query } = router;
const newQuery = { ...query };
delete newQuery[param];
router.replace({ pathname, query: newQuery }, undefined, { shallow: true });
}
import type { NextRouter } from 'next/router';
export default function updateQueryParam(router: NextRouter, param: string, newValue: string) {
const { pathname, query } = router;
const newQuery = { ...query };
newQuery[param] = newValue;
router.replace({ pathname, query: newQuery }, undefined, { shallow: true });
}
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 { SmartContractSecurityAudits } from 'types/api/contract';
export const contractAudits: SmartContractSecurityAudits = {
items: [
{
audit_company_name: 'OpenZeppelin',
audit_publish_date: '2023-03-01',
audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit',
},
{
audit_company_name: 'OpenZeppelin',
audit_publish_date: '2023-03-01',
audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit',
},
],
};
export const txnBatchesData = { export const txnBatchesData = {
items: [ items: [
{ {
epoch_number: 8547349,
l1_tx_hashes: [ l1_tx_hashes: [
'0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5', '0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5',
'0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d', '0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d',
...@@ -11,7 +10,6 @@ export const txnBatchesData = { ...@@ -11,7 +10,6 @@ export const txnBatchesData = {
tx_count: 0, tx_count: 0,
}, },
{ {
epoch_number: 8547348,
l1_tx_hashes: [ l1_tx_hashes: [
'0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa', '0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa',
], ],
...@@ -20,7 +18,6 @@ export const txnBatchesData = { ...@@ -20,7 +18,6 @@ export const txnBatchesData = {
tx_count: 0, tx_count: 0,
}, },
{ {
epoch_number: 8547348,
l1_tx_hashes: [ l1_tx_hashes: [
'0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8', '0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8',
], ],
......
import type { ShibariumDepositsResponse } from 'types/api/shibarium';
export const data: ShibariumDepositsResponse = {
items: [
{
l1_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l1_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l1_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
],
next_page_params: {
items_count: 50,
block_number: 8382363,
},
};
import type { ShibariumWithdrawalsResponse } from 'types/api/shibarium';
export const data: ShibariumWithdrawalsResponse = {
items: [
{
l2_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l2_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l2_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
],
next_page_params: {
items_count: 50,
block_number: 8382363,
},
};
import type { HomeStats } from 'types/api/stats'; import _mapValues from 'lodash/mapValues';
export const base: HomeStats = { export const base = {
average_block_time: 6212.0, average_block_time: 6212.0,
coin_price: '0.00199678', coin_price: '0.00199678',
coin_price_change_percentage: -7.42, coin_price_change_percentage: -7.42,
gas_prices: { gas_prices: {
average: { average: {
fiat_price: '1.01', fiat_price: '1.39',
price: 20.41, price: 23.75,
time: 12283, time: 12030.25,
base_fee: 2.22222,
priority_fee: 12.424242,
}, },
fast: { fast: {
fiat_price: '1.26', fiat_price: '1.74',
price: 25.47, price: 29.72,
time: 9321, time: 8763.25,
base_fee: 4.44444,
priority_fee: 22.242424,
}, },
slow: { slow: {
fiat_price: '0.97', fiat_price: '1.35',
price: 19.55, price: 23.04,
time: 24543, time: 20100.25,
base_fee: 1.11111,
priority_fee: 7.8909,
}, },
}, },
gas_price_updated_at: '2022-11-11T11:09:49.051171Z', gas_price_updated_at: '2022-11-11T11:09:49.051171Z',
...@@ -35,7 +41,22 @@ export const base: HomeStats = { ...@@ -35,7 +41,22 @@ export const base: HomeStats = {
tvl: '1767425.102766552', tvl: '1767425.102766552',
}; };
export const withBtcLocked: HomeStats = { export const withBtcLocked = {
...base, ...base,
rootstock_locked_btc: '3337493406696977561374', rootstock_locked_btc: '3337493406696977561374',
}; };
export const withoutFiatPrices = {
...base,
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null),
};
export const withoutGweiPrices = {
...base,
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null),
};
export const withoutBothPrices = {
...base,
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null),
};
export const averageGasPrice = {
chart: [
{
date: '2023-12-22',
value: '37.7804422597599',
},
{
date: '2023-12-23',
value: '25.84889883009387',
},
{
date: '2023-12-24',
value: '25.818463227198574',
},
{
date: '2023-12-25',
value: '26.045513050051298',
},
{
date: '2023-12-26',
value: '21.42600692652399',
},
{
date: '2023-12-27',
value: '31.066730409846656',
},
{
date: '2023-12-28',
value: '33.63955781902089',
},
{
date: '2023-12-29',
value: '28.064736756058384',
},
{
date: '2023-12-30',
value: '23.074500869678175',
},
{
date: '2023-12-31',
value: '17.651005734615133',
},
{
date: '2024-01-01',
value: '14.906085174476441',
},
{
date: '2024-01-02',
value: '22.28459059038656',
},
{
date: '2024-01-03',
value: '39.8311646806592',
},
{
date: '2024-01-04',
value: '26.09989322256083',
},
{
date: '2024-01-05',
value: '22.821996688111998',
},
{
date: '2024-01-06',
value: '20.32680041262083',
},
{
date: '2024-01-07',
value: '32.535045831809704',
},
{
date: '2024-01-08',
value: '27.443477102139482',
},
{
date: '2024-01-09',
value: '20.7911332558055',
},
{
date: '2024-01-10',
value: '42.10740192523919',
},
{
date: '2024-01-11',
value: '35.75215680343582',
},
{
date: '2024-01-12',
value: '27.430414798093253',
},
{
date: '2024-01-13',
value: '20.170934096589875',
},
{
date: '2024-01-14',
value: '38.79660984371034',
},
{
date: '2024-01-15',
value: '26.140740484554204',
},
{
date: '2024-01-16',
value: '36.708543184194156',
},
{
date: '2024-01-17',
value: '40.325438794298876',
},
{
date: '2024-01-18',
value: '37.55145309930694',
},
{
date: '2024-01-19',
value: '33.271450114434664',
},
{
date: '2024-01-20',
value: '19.303304377685638',
},
{
date: '2024-01-21',
value: '14.375908594704976',
},
],
};
export const base = {
sections: [
{
id: 'accounts',
title: 'Accounts',
charts: [
{
id: 'accountsGrowth',
title: 'Accounts growth',
description: 'Cumulative accounts number per period',
units: null,
},
{
id: 'activeAccounts',
title: 'Active accounts',
description: 'Active accounts number per period',
units: null,
},
{
id: 'newAccounts',
title: 'New accounts',
description: 'New accounts number per day',
units: null,
},
],
},
{
id: 'transactions',
title: 'Transactions',
charts: [
{
id: 'averageTxnFee',
title: 'Average transaction fee',
description: 'The average amount in ETH spent per transaction',
units: 'ETH',
},
{
id: 'newTxns',
title: 'New transactions',
description: 'New transactions number',
units: null,
},
{
id: 'txnsFee',
title: 'Transactions fees',
description: 'Amount of tokens paid as fees',
units: 'ETH',
},
{
id: 'txnsGrowth',
title: 'Transactions growth',
description: 'Cumulative transactions number',
units: null,
},
{
id: 'txnsSuccessRate',
title: 'Transactions success rate',
description: 'Successful transactions rate per day',
units: null,
},
],
},
{
id: 'blocks',
title: 'Blocks',
charts: [
{
id: 'averageBlockRewards',
title: 'Average block rewards',
description: 'Average amount of distributed reward in tokens per day',
units: 'ETH',
},
{
id: 'averageBlockSize',
title: 'Average block size',
description: 'Average size of blocks in bytes',
units: 'Bytes',
},
{
id: 'newBlocks',
title: 'New blocks',
description: 'New blocks number',
units: null,
},
],
},
{
id: 'tokens',
title: 'Tokens',
charts: [
{
id: 'newNativeCoinTransfers',
title: 'New ETH transfers',
description: 'New token transfers number for the period',
units: null,
},
],
},
{
id: 'gas',
title: 'Gas',
charts: [
{
id: 'averageGasLimit',
title: 'Average gas limit',
description: 'Average gas limit per block for the period',
units: null,
},
{
id: 'averageGasPrice',
title: 'Average gas price',
description: 'Average gas price for the period (Gwei)',
units: 'Gwei',
},
{
id: 'gasUsedGrowth',
title: 'Gas used growth',
description: 'Cumulative gas used for the period',
units: null,
},
],
},
{
id: 'contracts',
title: 'Contracts',
charts: [
{
id: 'newVerifiedContracts',
title: 'New verified contracts',
description: 'New verified contracts number for the period',
units: null,
},
{
id: 'verifiedContractsGrowth',
title: 'Verified contracts growth',
description: 'Cumulative number verified contracts for the period',
units: null,
},
],
},
],
};
import type { Validator, ValidatorsCountersResponse, ValidatorsResponse } from 'types/api/validators';
import * as addressMock from '../address/address';
export const validator1: Validator = {
address: addressMock.withName,
blocks_validated_count: 7334224,
state: 'active',
};
export const validator2: Validator = {
address: addressMock.withEns,
blocks_validated_count: 8937453,
state: 'probation',
};
export const validator3: Validator = {
address: addressMock.withoutName,
blocks_validated_count: 1234,
state: 'inactive',
};
export const validatorsResponse: ValidatorsResponse = {
items: [ validator1, validator2, validator3 ],
next_page_params: null,
};
export const validatorsCountersResponse: ValidatorsCountersResponse = {
active_validators_counter: '42',
active_validators_percentage: 7.14,
new_validators_counter_24h: '11',
validators_counter: '140',
};
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,53 @@ import sha256 from 'crypto-js/sha256'; ...@@ -3,30 +3,53 @@ 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',
'*.ixncdn.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',
], ],
}; };
......
...@@ -31,6 +31,8 @@ const getCspReportUrl = () => { ...@@ -31,6 +31,8 @@ const getCspReportUrl = () => {
}; };
export function app(): CspDev.DirectiveDescriptor { export function app(): CspDev.DirectiveDescriptor {
const marketplaceFeaturePayload = getFeaturePayload(config.features.marketplace);
return { return {
'default-src': [ 'default-src': [
// KEY_WORDS.NONE, // KEY_WORDS.NONE,
...@@ -54,6 +56,7 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -54,6 +56,7 @@ export function app(): CspDev.DirectiveDescriptor {
getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, getFeaturePayload(config.features.verifiedTokens)?.api.endpoint,
getFeaturePayload(config.features.addressVerification)?.api.endpoint, getFeaturePayload(config.features.addressVerification)?.api.endpoint,
getFeaturePayload(config.features.nameService)?.api.endpoint, getFeaturePayload(config.features.nameService)?.api.endpoint,
marketplaceFeaturePayload && 'api' in marketplaceFeaturePayload ? marketplaceFeaturePayload.api.endpoint : '',
// chain RPC server // chain RPC server
config.chain.rpcUrl, config.chain.rpcUrl,
......
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;
...@@ -48,8 +49,21 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => { ...@@ -48,8 +49,21 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
return account(context); return account(context);
}; };
export const deposits: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium'))) {
return {
notFound: true,
};
}
return base(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' || rollupFeature.type === 'shibarium'))
) {
return { return {
notFound: true, notFound: true,
}; };
...@@ -59,7 +73,7 @@ export const withdrawals: GetServerSideProps<Props> = async(context) => { ...@@ -59,7 +73,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 +83,7 @@ export const rollup: GetServerSideProps<Props> = async(context) => { ...@@ -69,7 +83,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 +93,7 @@ export const optimisticRollup: GetServerSideProps<Props> = async(context) => { ...@@ -79,7 +93,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,
}; };
...@@ -167,3 +181,23 @@ export const userOps: GetServerSideProps<Props> = async(context) => { ...@@ -167,3 +181,23 @@ export const userOps: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const validators: GetServerSideProps<Props> = async(context) => {
if (!config.features.validators.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const gasTracker: GetServerSideProps<Props> = async(context) => {
if (!config.features.gasTracker.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
...@@ -33,14 +33,15 @@ declare module "nextjs-routes" { ...@@ -33,14 +33,15 @@ declare module "nextjs-routes" {
| StaticRoute<"/contract-verification"> | StaticRoute<"/contract-verification">
| StaticRoute<"/csv-export"> | StaticRoute<"/csv-export">
| StaticRoute<"/deposits"> | StaticRoute<"/deposits">
| StaticRoute<"/gas-tracker">
| StaticRoute<"/graphiql"> | StaticRoute<"/graphiql">
| StaticRoute<"/"> | StaticRoute<"/">
| 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 }>
...@@ -49,6 +50,7 @@ declare module "nextjs-routes" { ...@@ -49,6 +50,7 @@ declare module "nextjs-routes" {
| DynamicRoute<"/tx/[hash]", { "hash": string }> | DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs"> | StaticRoute<"/txs">
| DynamicRoute<"/txs/kettle/[hash]", { "hash": string }> | DynamicRoute<"/txs/kettle/[hash]", { "hash": string }>
| StaticRoute<"/validators">
| StaticRoute<"/verified-contracts"> | StaticRoute<"/verified-contracts">
| StaticRoute<"/visualize/sol2uml"> | StaticRoute<"/visualize/sol2uml">
| StaticRoute<"/withdrawals">; | StaticRoute<"/withdrawals">;
......
...@@ -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",
......
...@@ -23,7 +23,7 @@ import Layout from 'ui/shared/layout/Layout'; ...@@ -23,7 +23,7 @@ import Layout from 'ui/shared/layout/Layout';
import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider';
import 'lib/setLocale'; import 'lib/setLocale';
import 'focus-visible/dist/focus-visible'; // import 'focus-visible/dist/focus-visible';
type AppPropsWithLayout = AppProps & { type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout; Component: NextPageWithLayout;
......
...@@ -4,31 +4,13 @@ import React from 'react'; ...@@ -4,31 +4,13 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app';
import LinkExternal from 'ui/shared/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle';
const feature = config.features.marketplace;
const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false }); const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => (
return ( <PageNextJs pathname="/apps">
<PageNextJs pathname="/apps"> <Marketplace/>
<> </PageNextJs>
<PageTitle );
title="DAppscout"
contentAfter={ feature.isEnabled && (
<LinkExternal href={ feature.submitFormUrl } variant="subtle" fontSize="sm" lineHeight={ 5 } ml="auto">
Submit app
</LinkExternal>
) }
/>
<Marketplace/>
</>
</PageNextJs>
);
};
export default Page; export default Page;
......
...@@ -5,12 +5,20 @@ import React from 'react'; ...@@ -5,12 +5,20 @@ 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.');
} }
return import('ui/pages/L2TxnBatches');
switch (rollupFeature.type) {
case 'zkEvm':
return import('ui/pages/ZkEvmL2TxnBatches');
case 'optimistic':
return import('ui/pages/OptimisticL2TxnBatches');
}
throw new Error('Txn batches feature is not enabled.');
}, { ssr: false }); }, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
......
...@@ -4,7 +4,20 @@ import React from 'react'; ...@@ -4,7 +4,20 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const Deposits = dynamic(() => import('ui/pages/L2Deposits'), { ssr: false }); import config from 'configs/app';
const rollupFeature = config.features.rollup;
const Deposits = dynamic(() => {
if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
return import('ui/pages/OptimisticL2Deposits');
}
if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
return import('ui/pages/ShibariumDeposits');
}
throw new Error('Withdrawals feature is not enabled.');
}, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
...@@ -16,4 +29,4 @@ const Page: NextPage = () => { ...@@ -16,4 +29,4 @@ const Page: NextPage = () => {
export default Page; export default Page;
export { optimisticRollup as getServerSideProps } from 'nextjs/getServerSideProps'; export { deposits as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const GasTracker = dynamic(() => import('ui/pages/GasTracker'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/gas-tracker">
<GasTracker/>
</PageNextJs>
);
};
export default Page;
export { gasTracker as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -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 (
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const Validators = dynamic(() => import('ui/pages/Validators'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/validators">
<Validators/>
</PageNextJs>
);
};
export default Page;
export { validators as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -5,12 +5,23 @@ import React from 'react'; ...@@ -5,12 +5,23 @@ 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 (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
return import('ui/pages/ShibariumWithdrawals');
}
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 = () => {
......
...@@ -26,8 +26,10 @@ const config: PlaywrightTestConfig = defineConfig({ ...@@ -26,8 +26,10 @@ const config: PlaywrightTestConfig = defineConfig({
/* Retry on CI only */ /* Retry on CI only */
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests. */
workers: process.env.CI ? 1 : undefined, // on non-performant local machines some tests may fail due to lack of resources
// so we opt out of parallel tests in any environment
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: 'html',
......
...@@ -3,6 +3,7 @@ import { devices } from '@playwright/test'; ...@@ -3,6 +3,7 @@ import { devices } from '@playwright/test';
export const viewport = { export const viewport = {
mobile: devices['iPhone 13 Pro'].viewport, mobile: devices['iPhone 13 Pro'].viewport,
md: { width: 1001, height: 800 },
xl: { width: 1600, height: 1000 }, xl: { width: 1600, height: 1000 },
}; };
...@@ -15,9 +16,13 @@ export const featureEnvs = { ...@@ -15,9 +16,13 @@ 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' },
],
shibariumRollup: [
{ name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'shibarium' },
{ name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
], ],
bridgedTokens: [ bridgedTokens: [
{ {
...@@ -32,13 +37,16 @@ export const featureEnvs = { ...@@ -32,13 +37,16 @@ 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' },
], ],
validators: [
{ name: 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', value: 'stability' },
],
}; };
export const viewsEnvs = { export const viewsEnvs = {
...@@ -49,6 +57,12 @@ export const viewsEnvs = { ...@@ -49,6 +57,12 @@ export const viewsEnvs = {
}, },
}; };
export const UIEnvs = {
hasContractAuditReports: [
{ name: 'NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS', value: 'true' },
],
};
export const stabilityEnvs = [ export const stabilityEnvs = [
{ name: 'NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS', value: '["top_accounts"]' }, { name: 'NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS', value: '["top_accounts"]' },
{ name: 'NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS', value: '["value","fee_currency","gas_price","gas_fees","burnt_fees"]' }, { name: 'NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS', value: '["value","fee_currency","gas_price","gas_fees","burnt_fees"]' },
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
| "filter" | "filter"
| "finalized" | "finalized"
| "flame" | "flame"
| "gas_xl"
| "gas" | "gas"
| "gear" | "gear"
| "globe-b" | "globe-b"
...@@ -82,6 +83,7 @@ ...@@ -82,6 +83,7 @@
| "qr_code" | "qr_code"
| "repeat_arrow" | "repeat_arrow"
| "restAPI" | "restAPI"
| "rocket_xl"
| "rocket" | "rocket"
| "RPC" | "RPC"
| "scope" | "scope"
...@@ -115,6 +117,7 @@ ...@@ -115,6 +117,7 @@
| "status/success" | "status/success"
| "status/warning" | "status/warning"
| "sun" | "sun"
| "swap"
| "testnet" | "testnet"
| "token-placeholder" | "token-placeholder"
| "token" | "token"
...@@ -129,6 +132,7 @@ ...@@ -129,6 +132,7 @@
| "uniswap" | "uniswap"
| "user_op_slim" | "user_op_slim"
| "user_op" | "user_op"
| "validator"
| "verified_token" | "verified_token"
| "verified" | "verified"
| "verify-contract" | "verify-contract"
......
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,8 +28,7 @@ export const L2_WITHDRAWAL_ITEM: L2WithdrawalsItem = { ...@@ -26,8 +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,
l1_timestamp: '2023-06-01T14:46:48.000000Z', l1_timestamp: '2023-06-01T14:46:48.000000Z',
l1_tx_hashes: [ l1_tx_hashes: [
TX_HASH, TX_HASH,
...@@ -36,7 +37,7 @@ export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = { ...@@ -36,7 +37,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 { SmartContract, SolidityscanReport } from 'types/api/contract'; import type { SmartContract, SolidityscanReport } from 'types/api/contract';
import type { VerifiedContract } from 'types/api/contracts'; import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts';
import { ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_PARAMS } from './addressParams';
...@@ -54,6 +54,13 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = { ...@@ -54,6 +54,13 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = {
verified_at: '2023-04-10T13:16:33.884921Z', verified_at: '2023-04-10T13:16:33.884921Z',
}; };
export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = {
smart_contracts: '123456789',
new_smart_contracts_24h: '12345',
verified_smart_contracts: '654321',
new_verified_smart_contracts_24h: '1234',
};
export const SOLIDITYSCAN_REPORT: SolidityscanReport = { export const SOLIDITYSCAN_REPORT: SolidityscanReport = {
scan_report: { scan_report: {
scan_status: 'scan_done', scan_status: 'scan_done',
......
import type { ShibariumDepositsItem, ShibariumWithdrawalsItem } from 'types/api/shibarium';
import { ADDRESS_PARAMS } from './addressParams';
import { TX_HASH } from './tx';
export const SHIBARIUM_DEPOSIT_ITEM: ShibariumDepositsItem = {
l1_block_number: 9045233,
l1_transaction_hash: TX_HASH,
l2_transaction_hash: TX_HASH,
timestamp: '2023-05-22T18:00:36.000000Z',
user: ADDRESS_PARAMS,
};
export const SHIBARIUM_WITHDRAWAL_ITEM: ShibariumWithdrawalsItem = {
l2_block_number: 9045233,
l1_transaction_hash: TX_HASH,
l2_transaction_hash: TX_HASH,
timestamp: '2023-05-22T18:00:36.000000Z',
user: ADDRESS_PARAMS,
};
...@@ -9,16 +9,22 @@ export const HOMEPAGE_STATS: HomeStats = { ...@@ -9,16 +9,22 @@ export const HOMEPAGE_STATS: HomeStats = {
fiat_price: '1.01', fiat_price: '1.01',
price: 20.41, price: 20.41,
time: 12283, time: 12283,
base_fee: 2.22222,
priority_fee: 12.424242,
}, },
fast: { fast: {
fiat_price: '1.26', fiat_price: '1.26',
price: 25.47, price: 25.47,
time: 9321, time: 9321,
base_fee: 4.44444,
priority_fee: 22.242424,
}, },
slow: { slow: {
fiat_price: '0.97', fiat_price: '0.97',
price: 19.55, price: 19.55,
time: 24543, time: 24543,
base_fee: 1.11111,
priority_fee: 7.8909,
}, },
}, },
gas_price_updated_at: '2022-11-11T11:09:49.051171Z', gas_price_updated_at: '2022-11-11T11:09:49.051171Z',
......
import type { ArrayElement } from 'types/utils'; import type { ArrayElement } from 'types/utils';
import type { PaginatedResources, PaginatedResponse } from 'lib/api/resources'; import type { PaginatedResources, PaginatedResponse, PaginatedResponseItems } from 'lib/api/resources';
export function generateListStub<Resource extends PaginatedResources>( export function generateListStub<Resource extends PaginatedResources>(
stub: ArrayElement<PaginatedResponse<Resource>['items']>, stub: ArrayElement<PaginatedResponseItems<Resource>>,
num = 50, num = 50,
rest: Omit<PaginatedResponse<Resource>, 'items'>, rest: Omit<PaginatedResponse<Resource>, 'items'>,
) { ) {
......
import type { Validator, ValidatorsCountersResponse } from 'types/api/validators';
import { ADDRESS_PARAMS } from './addressParams';
export const VALIDATOR: Validator = {
address: ADDRESS_PARAMS,
blocks_validated_count: 25987,
state: 'active',
};
export const VALIDATORS_COUNTERS: ValidatorsCountersResponse = {
active_validators_counter: '42',
active_validators_percentage: 7.14,
new_validators_counter_24h: '11',
validators_counter: '140',
};
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';
......
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import { getColor, mode } from '@chakra-ui/theme-tools'; import { getColor } from '@chakra-ui/theme-tools';
import getDefaultFormColors from '../utils/getDefaultFormColors'; import getDefaultFormColors from '../utils/getDefaultFormColors';
...@@ -20,7 +20,7 @@ const baseStyle = defineStyle({ ...@@ -20,7 +20,7 @@ const baseStyle = defineStyle({
const variantFloating = defineStyle((props) => { const variantFloating = defineStyle((props) => {
const { theme, backgroundColor } = props; const { theme, backgroundColor } = props;
const { focusPlaceholderColor } = getDefaultFormColors(props); const { focusPlaceholderColor } = getDefaultFormColors(props);
const bc = backgroundColor || mode('white', 'black')(props); const bc = backgroundColor || 'transparent';
return { return {
left: '2px', left: '2px',
......
...@@ -49,6 +49,13 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) { ...@@ -49,6 +49,13 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
}, },
// not filled input // not filled input
':placeholder-shown:not(:focus-visible):not(:hover):not([aria-invalid=true])': { borderColor: borderColor || mode('gray.100', 'gray.700')(props) }, ':placeholder-shown:not(:focus-visible):not(:hover):not([aria-invalid=true])': { borderColor: borderColor || mode('gray.100', 'gray.700')(props) },
// not filled input with type="date"
':not(:placeholder-shown)[value=""]:not(:focus-visible):not(:hover):not([aria-invalid=true])': {
borderColor: borderColor || mode('gray.100', 'gray.700')(props),
color: 'gray.500',
},
':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' }, ':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' },
':-webkit-autofill:hover': { transition: 'background-color 5000s ease-in-out 0s' }, ':-webkit-autofill:hover': { transition: 'background-color 5000s ease-in-out 0s' },
':-webkit-autofill:focus': { transition: 'background-color 5000s ease-in-out 0s' }, ':-webkit-autofill:focus': { transition: 'background-color 5000s ease-in-out 0s' },
......
...@@ -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;
} }
......
...@@ -55,7 +55,7 @@ export interface SmartContractExternalLibrary { ...@@ -55,7 +55,7 @@ export interface SmartContractExternalLibrary {
export interface SmartContractMethodBase { export interface SmartContractMethodBase {
inputs: Array<SmartContractMethodInput>; inputs: Array<SmartContractMethodInput>;
outputs: Array<SmartContractMethodOutput>; outputs?: Array<SmartContractMethodOutput>;
constant: boolean; constant: boolean;
name: string; name: string;
stateMutability: SmartContractMethodStateMutability; stateMutability: SmartContractMethodStateMutability;
...@@ -180,3 +180,26 @@ export type SolidityscanReport = { ...@@ -180,3 +180,26 @@ export type SolidityscanReport = {
scanner_reference_url: string; scanner_reference_url: string;
}; };
} }
type SmartContractSecurityAudit = {
audit_company_name: string;
audit_publish_date: string;
audit_report_url: string;
}
export type SmartContractSecurityAudits = {
items: Array<SmartContractSecurityAudit>;
}
export type SmartContractSecurityAuditSubmission = {
'address_hash': string;
'submitter_name': string;
'submitter_email': string;
'is_project_owner': boolean;
'project_name': string;
'project_url': string;
'audit_company_name': string;
'audit_report_url': string;
'audit_publish_date': string;
'comment'?: string;
}
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 = {
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;
};
}
import type { AddressParam } from './addressParams';
export type ShibariumDepositsItem = {
l1_block_number: number;
l1_transaction_hash: string;
l2_transaction_hash: string;
timestamp: string;
user: AddressParam | string;
}
export type ShibariumDepositsResponse = {
items: Array<ShibariumDepositsItem>;
next_page_params: {
items_count: number;
block_number: number;
};
}
export type ShibariumWithdrawalsItem = {
l1_transaction_hash: string;
l2_block_number: number;
l2_transaction_hash: string;
timestamp: string;
user: AddressParam | string;
}
export type ShibariumWithdrawalsResponse = {
items: Array<ShibariumWithdrawalsItem>;
next_page_params: {
items_count: number;
block_number: number;
};
}
...@@ -28,6 +28,8 @@ export interface GasPriceInfo { ...@@ -28,6 +28,8 @@ export interface GasPriceInfo {
fiat_price: string | null; fiat_price: string | null;
price: number | null; price: number | null;
time: number | null; time: number | null;
base_fee: number | null;
priority_fee: number | null;
} }
export type Counters = { export type Counters = {
......
...@@ -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 = {
......
...@@ -18,9 +18,10 @@ export type TxInterpretationVariable = ...@@ -18,9 +18,10 @@ export type TxInterpretationVariable =
TxInterpretationVariableTimestamp | TxInterpretationVariableTimestamp |
TxInterpretationVariableToken | TxInterpretationVariableToken |
TxInterpretationVariableAddress | TxInterpretationVariableAddress |
TxInterpretationVariableDomain; TxInterpretationVariableDomain |
TxInterpretationVariableMethod;
export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address' | 'domain'; export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address' | 'domain' | 'method';
export type TxInterpretationVariableString = { export type TxInterpretationVariableString = {
type: 'string'; type: 'string';
...@@ -51,3 +52,8 @@ export type TxInterpretationVariableDomain = { ...@@ -51,3 +52,8 @@ export type TxInterpretationVariableDomain = {
type: 'domain'; type: 'domain';
value: string; value: string;
} }
export type TxInterpretationVariableMethod = {
type: 'method';
value: string;
}
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.
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.
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