Merge branch 'main' of github.com:blockscout/frontend into chakra-v3
@@ -6,4 +6,8 @@ NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx
|
||||
NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx
|
||||
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
|
||||
NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx
|
||||
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=xxx
|
||||
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=xxx
|
||||
|
||||
## DEPRECATED
|
||||
NEXT_PUBLIC_SENTRY_DSN=xxx
|
||||
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx
|
||||
@@ -17,6 +17,9 @@
|
||||
/public/icons/sprite.svg
|
||||
/public/icons/sprite.*.svg
|
||||
/public/icons/README.md
|
||||
/public/static/og_image.png
|
||||
/public/sitemap.xml
|
||||
/public/robots.txt
|
||||
/analyze
|
||||
|
||||
# production
|
||||
|
||||
@@ -33,6 +33,12 @@ WORKDIR /favicon-generator
|
||||
COPY ./deploy/tools/favicon-generator/package.json ./deploy/tools/favicon-generator/yarn.lock ./
|
||||
RUN yarn --frozen-lockfile --network-timeout 100000
|
||||
|
||||
### SITEMAP GENERATOR
|
||||
# Install dependencies
|
||||
WORKDIR /sitemap-generator
|
||||
COPY ./deploy/tools/sitemap-generator/package.json ./deploy/tools/sitemap-generator/yarn.lock ./
|
||||
RUN yarn --frozen-lockfile --network-timeout 100000
|
||||
|
||||
|
||||
# *****************************
|
||||
# ****** STAGE 2: Build *******
|
||||
@@ -88,6 +94,12 @@ RUN cd ./deploy/tools/envs-validator && yarn build
|
||||
# Copy dependencies and source code
|
||||
COPY --from=deps /favicon-generator/node_modules ./deploy/tools/favicon-generator/node_modules
|
||||
|
||||
|
||||
### SITEMAP GENERATOR
|
||||
# Copy dependencies and source code
|
||||
COPY --from=deps /sitemap-generator/node_modules ./deploy/tools/sitemap-generator/node_modules
|
||||
|
||||
|
||||
# *****************************
|
||||
# ******* STAGE 3: Run ********
|
||||
# *****************************
|
||||
@@ -122,11 +134,16 @@ COPY --chmod=755 ./deploy/scripts/validate_envs.sh .
|
||||
COPY --chmod=755 ./deploy/scripts/make_envs_script.sh .
|
||||
## Assets downloader
|
||||
COPY --chmod=755 ./deploy/scripts/download_assets.sh .
|
||||
## OG image generator
|
||||
COPY ./deploy/scripts/og_image_generator.js .
|
||||
## Favicon generator
|
||||
COPY --chmod=755 ./deploy/scripts/favicon_generator.sh .
|
||||
COPY --from=builder /app/deploy/tools/favicon-generator ./deploy/tools/favicon-generator
|
||||
RUN ["chmod", "-R", "777", "./deploy/tools/favicon-generator"]
|
||||
RUN ["chmod", "-R", "777", "./public"]
|
||||
## Sitemap generator
|
||||
COPY --chmod=755 ./deploy/scripts/sitemap_generator.sh .
|
||||
COPY --from=builder /app/deploy/tools/sitemap-generator ./deploy/tools/sitemap-generator
|
||||
|
||||
# Copy ENVs files
|
||||
COPY --from=builder /app/.env.registry .
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { RollupType } from 'types/client/rollup';
|
||||
import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks';
|
||||
|
||||
import { getEnvValue } from './utils';
|
||||
import { urlValidator } from 'ui/shared/forms/validators/url';
|
||||
|
||||
import { getEnvValue, parseEnvJson } from './utils';
|
||||
|
||||
const DEFAULT_CURRENCY_DECIMALS = 18;
|
||||
|
||||
@@ -17,6 +19,19 @@ const verificationType: NetworkVerificationType = (() => {
|
||||
return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining';
|
||||
})();
|
||||
|
||||
const rpcUrls = (() => {
|
||||
const envValue = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
|
||||
const isUrl = urlValidator(envValue);
|
||||
|
||||
if (envValue && isUrl === true) {
|
||||
return [ envValue ];
|
||||
}
|
||||
|
||||
const parsedValue = parseEnvJson<Array<string>>(envValue);
|
||||
|
||||
return Array.isArray(parsedValue) ? parsedValue : [];
|
||||
})();
|
||||
|
||||
const chain = Object.freeze({
|
||||
id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'),
|
||||
name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'),
|
||||
@@ -32,7 +47,7 @@ const chain = Object.freeze({
|
||||
},
|
||||
hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true',
|
||||
tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC',
|
||||
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'),
|
||||
rpcUrls,
|
||||
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
|
||||
verificationType,
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ const config: Feature<{ walletConnect: { projectId: string } }> = (() => {
|
||||
chain.currency.name &&
|
||||
chain.currency.symbol &&
|
||||
chain.currency.decimals &&
|
||||
chain.rpcUrl &&
|
||||
chain.rpcUrls.length > 0 &&
|
||||
walletConnectProjectId
|
||||
) {
|
||||
return Object.freeze({
|
||||
|
||||
@@ -33,7 +33,7 @@ const config: Feature<(
|
||||
rating: { airtableApiKey: string; airtableBaseId: string } | undefined;
|
||||
graphLinksUrl: string | undefined;
|
||||
}> = (() => {
|
||||
if (enabled === 'true' && chain.rpcUrl && submitFormUrl) {
|
||||
if (enabled === 'true' && chain.rpcUrls.length > 0 && submitFormUrl) {
|
||||
const props = {
|
||||
submitFormUrl,
|
||||
categoriesUrl,
|
||||
|
||||
@@ -31,7 +31,7 @@ const config: Feature<{
|
||||
type,
|
||||
L1BaseUrl: stripTrailingSlash(L1BaseUrl),
|
||||
L2WithdrawalUrl: type === 'optimistic' ? L2WithdrawalUrl : undefined,
|
||||
outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') !== 'false',
|
||||
outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') === 'true',
|
||||
parentChainName: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME') : undefined,
|
||||
homepage: {
|
||||
showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import app from './app';
|
||||
import { getEnvValue, getExternalAssetFilePath } from './utils';
|
||||
|
||||
const defaultImageUrl = '/static/og_placeholder.png';
|
||||
const defaultImageUrl = '/static/og_image.png';
|
||||
|
||||
const meta = Object.freeze({
|
||||
promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'false' ? false : true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Set of ENVs for Shibarium network explorer
|
||||
# https://www.shibariumscan.io
|
||||
# This is an auto-generated file. To update all values, run "yarn preset:sync --name=shibarium"
|
||||
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=shibarium"
|
||||
|
||||
# Local ENVs
|
||||
NEXT_PUBLIC_APP_PROTOCOL=http
|
||||
@@ -23,6 +23,7 @@ NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethere
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
|
||||
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}]
|
||||
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/shibarium-mainnet.json
|
||||
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge
|
||||
NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'}
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xce531d29c0c469fb00b443b8091b8c059b4f13d7e025dd0ef843401d02b9a1a9
|
||||
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true
|
||||
@@ -31,15 +32,17 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap']
|
||||
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(180deg, rgba(224, 111, 44, 1) 0%, rgba(228, 144, 52, 1) 100%)'],'text_color':['rgba(255, 255, 255, 1)']}
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
|
||||
NEXT_PUBLIC_LOGOUT_URL=https://shibarium.us.auth0.com/v2/logout
|
||||
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=<p>Participated in our recent Blockscout activities? <a href="https://badges.blockscout.com?utm_source=instance&utm_medium=shibarium" target="_blank">Check your eligibility</a> and claim your NFT Scout badges. More exciting things are coming soon!</p>
|
||||
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=<p>Joined recent campaigns? Mint your Merit Badge <a href="https://badges.blockscout.com?utm_source=instance&utm_medium=shibarium">here</a></p>
|
||||
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html
|
||||
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=shibarium
|
||||
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
|
||||
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=patbqG4V2CI998jAq.9810c58c9de973ba2650621c94559088cbdfa1a914498e385621ed035d33c0d0
|
||||
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs
|
||||
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
|
||||
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
|
||||
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=BONE
|
||||
|
||||
@@ -13,23 +13,31 @@ NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" }
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" }
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler
|
||||
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_HOST=zkevm.blockscout.com
|
||||
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
|
||||
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
|
||||
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zkevm.json
|
||||
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x25fcb396fc8652dcd0040f677a1dcc6fecff390ecafc815894379a3f254f1aa9
|
||||
NEXT_PUBLIC_HAS_USER_OPS=true
|
||||
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%)
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1)
|
||||
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
|
||||
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%)'],'text_color':['rgba(255, 255, 255, 1)']}
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
|
||||
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-polygon.us.auth0.com/v2/logout
|
||||
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
|
||||
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
NEXT_PUBLIC_METASUITES_ENABLED=true
|
||||
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
|
||||
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/polygon-zkevm/pools'}}]
|
||||
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg
|
||||
NEXT_PUBLIC_NETWORK_ID=1101
|
||||
|
||||
@@ -35,6 +35,9 @@ export_envs_from_preset() {
|
||||
# If there is a preset, load the environment variables from the its file
|
||||
export_envs_from_preset
|
||||
|
||||
# Generate OG image
|
||||
node --no-warnings ./og_image_generator.js
|
||||
|
||||
# Download external assets
|
||||
./download_assets.sh ./public/assets/configs
|
||||
|
||||
@@ -61,6 +64,9 @@ echo
|
||||
# Create envs.js file with run-time environment variables for the client app
|
||||
./make_envs_script.sh
|
||||
|
||||
# Generate sitemap.xml and robots.txt files
|
||||
./sitemap_generator.sh
|
||||
|
||||
# Print list of enabled features
|
||||
node ./feature-reporter.js
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
console.log('🎨 Generating OG image...');
|
||||
|
||||
const targetFile = path.resolve(process.cwd(), 'public/static/og_image.png');
|
||||
|
||||
function copyPlaceholderImage() {
|
||||
const sourceFile = path.resolve(process.cwd(), 'public/static/og_placeholder.png');
|
||||
fs.copyFileSync(sourceFile, targetFile);
|
||||
}
|
||||
|
||||
if (process.env.NEXT_PUBLIC_OG_IMAGE_URL) {
|
||||
console.log('⏩ NEXT_PUBLIC_OG_IMAGE_URL is set. Skipping OG image generation...');
|
||||
} else if (!process.env.NEXT_PUBLIC_NETWORK_NAME) {
|
||||
console.log('⏩ NEXT_PUBLIC_NETWORK_NAME is not set. Copying placeholder image...');
|
||||
copyPlaceholderImage();
|
||||
} else if (!process.env.NEXT_PUBLIC_NETWORK_LOGO && !process.env.NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG) {
|
||||
console.log('⏩ Neither NEXT_PUBLIC_NETWORK_LOGO nor NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG is set. Copying placeholder image...');
|
||||
copyPlaceholderImage();
|
||||
} else {
|
||||
try {
|
||||
const bannerConfig = JSON.parse(process.env.NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG?.replaceAll('\'', '"') || '{}');
|
||||
const data = {
|
||||
title: `${ process.env.NEXT_PUBLIC_NETWORK_NAME } explorer`,
|
||||
logo_url: process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK ?? process.env.NEXT_PUBLIC_NETWORK_LOGO,
|
||||
background: bannerConfig.background?.[0],
|
||||
title_color: bannerConfig.text_color?.[0],
|
||||
invert_logo: !process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK,
|
||||
};
|
||||
|
||||
console.log('⏳ Making request to OG image generator service...');
|
||||
|
||||
const response = await fetch('https://bigs.services.blockscout.com/generate/og', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('⬇️ Downloading the image...');
|
||||
const buffer = await response.arrayBuffer();
|
||||
const imageBuffer = Buffer.from(buffer);
|
||||
fs.writeFileSync(targetFile, imageBuffer);
|
||||
} else {
|
||||
const payload = response.headers.get('Content-type')?.includes('application/json') ? await response.json() : await response.text();
|
||||
console.error('🛑 Failed to generate OG image. Response:', payload);
|
||||
console.log('Copying placeholder image...');
|
||||
copyPlaceholderImage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🛑 Failed to generate OG image. Error:', error?.message);
|
||||
console.log('Copying placeholder image...');
|
||||
copyPlaceholderImage();
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Done.');
|
||||
@@ -0,0 +1,2 @@
|
||||
cd ./deploy/tools/sitemap-generator
|
||||
yarn next-sitemap
|
||||
@@ -587,7 +587,21 @@ const schema = yup
|
||||
NEXT_PUBLIC_NETWORK_NAME: yup.string().required(),
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME: yup.string(),
|
||||
NEXT_PUBLIC_NETWORK_ID: yup.number().positive().integer().required(),
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL: yup.string().test(urlTest),
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL: yup
|
||||
.mixed()
|
||||
.test(
|
||||
'shape',
|
||||
'Invalid schema were provided for NEXT_PUBLIC_NETWORK_RPC_URL, it should be either array of URLs or URL string',
|
||||
(data) => {
|
||||
const isUrlSchema = yup.string().test(urlTest);
|
||||
const isArrayOfUrlsSchema = yup
|
||||
.array()
|
||||
.transform(replaceQuotes)
|
||||
.json()
|
||||
.of(yup.string().test(urlTest));
|
||||
|
||||
return isUrlSchema.isValidSync(data) || isArrayOfUrlsSchema.isValidSync(data);
|
||||
}),
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: yup.string(),
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(),
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(),
|
||||
|
||||
@@ -5,4 +5,5 @@ NEXT_PUBLIC_HOMEPAGE_STATS=[]
|
||||
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32']
|
||||
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo
|
||||
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
|
||||
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated
|
||||
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=['https://example.com','https://example2.com']
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/node_modules
|
||||
@@ -0,0 +1,176 @@
|
||||
/* eslint-disable no-console */
|
||||
const path = require('path');
|
||||
|
||||
const stripTrailingSlash = (str) => str[str.length - 1] === '/' ? str.slice(0, -1) : str;
|
||||
|
||||
const fetchResource = async(url, formatter) => {
|
||||
console.log('🌀 [next-sitemap] Fetching resource:', url);
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
console.log('✅ [next-sitemap] Data fetched for resource:', url);
|
||||
return formatter(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('🚨 [next-sitemap] Error fetching resource:', url, error);
|
||||
}
|
||||
};
|
||||
|
||||
const siteUrl = [
|
||||
process.env.NEXT_PUBLIC_APP_PROTOCOL || 'https',
|
||||
'://',
|
||||
process.env.NEXT_PUBLIC_APP_HOST,
|
||||
process.env.NEXT_PUBLIC_APP_PORT && ':' + process.env.NEXT_PUBLIC_APP_PORT,
|
||||
].filter(Boolean).join('');
|
||||
|
||||
const apiUrl = (() => {
|
||||
const baseUrl = [
|
||||
process.env.NEXT_PUBLIC_API_PROTOCOL || 'https',
|
||||
'://',
|
||||
process.env.NEXT_PUBLIC_API_HOST,
|
||||
process.env.NEXT_PUBLIC_API_PORT && ':' + process.env.NEXT_PUBLIC_API_PORT,
|
||||
].filter(Boolean).join('');
|
||||
|
||||
const basePath = stripTrailingSlash(process.env.NEXT_PUBLIC_API_BASE_PATH || '');
|
||||
|
||||
return `${ baseUrl }${ basePath }/api/v2`;
|
||||
})();
|
||||
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl,
|
||||
generateIndexSitemap: false,
|
||||
generateRobotsTxt: true,
|
||||
sourceDir: path.resolve(process.cwd(), '../../../.next'),
|
||||
outDir: path.resolve(process.cwd(), '../../../public'),
|
||||
exclude: [
|
||||
'/account/*',
|
||||
'/auth/*',
|
||||
'/login',
|
||||
'/sprite',
|
||||
],
|
||||
transform: async(config, path) => {
|
||||
switch (path) {
|
||||
case '/mud-worlds':
|
||||
if (process.env.NEXT_PUBLIC_HAS_MUD_FRAMEWORK !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/batches':
|
||||
case '/deposits':
|
||||
if (!process.env.NEXT_PUBLIC_ROLLUP_TYPE) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/withdrawals':
|
||||
if (!process.env.NEXT_PUBLIC_ROLLUP_TYPE && process.env.NEXT_PUBLIC_HAS_BEACON_CHAIN !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/dispute-games':
|
||||
if (process.env.NEXT_PUBLIC_ROLLUP_TYPE !== 'optimistic') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/blobs':
|
||||
if (process.env.NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/name-domains':
|
||||
if (!process.env.NEXT_PUBLIC_NAME_SERVICE_API_HOST) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/ops':
|
||||
if (process.env.NEXT_PUBLIC_HAS_USER_OPS !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/output-roots':
|
||||
if (process.env.NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/pools':
|
||||
if (process.env.NEXT_PUBLIC_DEX_POOLS_ENABLED !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/advanced-filter':
|
||||
if (process.env.NEXT_PUBLIC_ADVANCED_FILTER_ENABLED === 'false') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/apps':
|
||||
if (process.env.NEXT_PUBLIC_MARKETPLACE_ENABLED !== 'true') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/api-docs':
|
||||
if (process.env.NEXT_PUBLIC_API_SPEC_URL === 'none') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/gas-tracker':
|
||||
if (process.env.NEXT_PUBLIC_GAS_TRACKER_ENABLED === 'false') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/graphql':
|
||||
if (process.env.NEXT_PUBLIC_GRAPHIQL_TRANSACTION === 'none') {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/stats':
|
||||
if (!process.env.NEXT_PUBLIC_STATS_API_HOST) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '/validators':
|
||||
if (!process.env.NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
loc: path,
|
||||
changefreq: undefined,
|
||||
priority: undefined,
|
||||
lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
|
||||
alternateRefs: config.alternateRefs ?? [],
|
||||
};
|
||||
},
|
||||
additionalPaths: async(config) => {
|
||||
const addresses = fetchResource(
|
||||
`${ apiUrl }/addresses`,
|
||||
(data) => data.items.map(({ hash }) => `/address/${ hash }`),
|
||||
);
|
||||
const txs = fetchResource(
|
||||
`${ apiUrl }/transactions?filter=validated`,
|
||||
(data) => data.items.map(({ hash }) => `/tx/${ hash }`),
|
||||
);
|
||||
const blocks = fetchResource(
|
||||
`${ apiUrl }/blocks?type=block`,
|
||||
(data) => data.items.map(({ height }) => `/block/${ height }`),
|
||||
);
|
||||
const tokens = fetchResource(
|
||||
`${ apiUrl }/tokens`,
|
||||
(data) => data.items.map(({ address }) => `/token/${ address }`),
|
||||
);
|
||||
const contracts = fetchResource(
|
||||
`${ apiUrl }/smart-contracts`,
|
||||
(data) => data.items.map(({ address }) => `/address/${ address.hash }?tab=contract`),
|
||||
);
|
||||
|
||||
return Promise.all([
|
||||
...(await addresses || []),
|
||||
...(await txs || []),
|
||||
...(await blocks || []),
|
||||
...(await tokens || []),
|
||||
...(await contracts || []),
|
||||
].map(path => config.transform(config, path)));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "sitemap-generator",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"next-sitemap": "4.2.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@corex/deepmerge@^4.0.43":
|
||||
version "4.0.43"
|
||||
resolved "https://registry.yarnpkg.com/@corex/deepmerge/-/deepmerge-4.0.43.tgz#9bd42559ebb41cc5a7fb7cfeea5f231c20977dca"
|
||||
integrity sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==
|
||||
|
||||
"@next/env@^13.4.3":
|
||||
version "13.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.8.tgz#404d3b3e5881b6a0510500c6cc97e3589a2e6371"
|
||||
integrity sha512-YmiG58BqyZ2FjrF2+5uZExL2BrLr8RTQzLXNDJ8pJr0O+rPlOeDPXp1p1/4OrR3avDidzZo3D8QO2cuDv1KCkw==
|
||||
|
||||
"@nodelib/[email protected]":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/[email protected]", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
||||
"@nodelib/fs.walk@^1.2.3":
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
dependencies:
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
braces@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
fast-glob@^3.2.12:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
||||
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.18.0.tgz#d631d7e25faffea81887fe5ea8c9010e1b36fee0"
|
||||
integrity sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
glob-parent@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||
|
||||
is-glob@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
merge2@^1.3.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
|
||||
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||
dependencies:
|
||||
braces "^3.0.3"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
minimist@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
[email protected]:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/next-sitemap/-/next-sitemap-4.2.3.tgz#5db3f650351a51e84b9fd6b58c5af2f9257b5058"
|
||||
integrity sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==
|
||||
dependencies:
|
||||
"@corex/deepmerge" "^4.0.43"
|
||||
"@next/env" "^13.4.3"
|
||||
fast-glob "^3.2.12"
|
||||
minimist "^1.2.8"
|
||||
|
||||
picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
@@ -95,7 +95,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
|
||||
| NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_SHORT_NAME | `string` | Used for SEO attributes (e.g, page description) | - | - | `OoG` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_ID | `number` | Chain id, see [https://chainlist.org](https://chainlist.org) for the reference | Required | - | `99` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference | - | - | `https://core.poa.network` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_RPC_URL | `string \| Array<string>` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference. Can contain a single string value, or an array of urls. | - | - | `https://core.poa.network` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | Network currency name | - | - | `Ether` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of network currency subdenomination | - | `wei` | `duck` | v1.23.0+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ |
|
||||
@@ -455,7 +455,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
|
||||
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ |
|
||||
| NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `true` | `false` | v1.37.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `false` | `true` | v1.37.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME | `string` | Set to customize L1 transaction status labels in the UI (e.g., "Sent to <chain-name>"). This setting is applicable only for Arbitrum-based chains. | - | - | `DuckChain` | v1.37.0+ |
|
||||
|
||||
|
||||
|
||||
@@ -33,14 +33,9 @@ const RESTRICTED_MODULES = {
|
||||
],
|
||||
message: 'Please use corresponding component or hook from ui/shared/chakra component instead',
|
||||
},
|
||||
{
|
||||
name: 'lodash',
|
||||
message: 'Please use `import [package] from \'lodash/[package]\'` instead.',
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
'icons/*',
|
||||
'!lodash/*',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -432,6 +427,7 @@ export default tseslint.config(
|
||||
'pages/**',
|
||||
'nextjs/**',
|
||||
'playwright/**',
|
||||
'deploy/scripts/**',
|
||||
'deploy/tools/**',
|
||||
'middleware.ts',
|
||||
'instrumentation*.ts',
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.444 18.315a10 10 0 1 0 11.112-16.63 10 10 0 0 0-11.112 16.63ZM5.3 2.965a8.462 8.462 0 1 1 9.402 14.07 8.462 8.462 0 0 1-9.402-14.07Zm8.637 11.978a.768.768 0 0 0 .295.057.769.769 0 0 0 .546-1.315l-4.008-4V3.846a.769.769 0 1 0-1.538 0V10a.77.77 0 0 0 .223.546l4.23 4.23a.77.77 0 0 0 .252.167Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.444 18.315a10 10 0 1 0 11.112-16.63 10 10 0 0 0-11.112 16.63ZM5.3 2.965a8.462 8.462 0 1 1 9.402 14.07A8.462 8.462 0 0 1 5.3 2.965Zm8.637 11.978a.768.768 0 0 0 .295.057.769.769 0 0 0 .546-1.315l-4.008-4V3.846a.769.769 0 1 0-1.538 0V10a.77.77 0 0 0 .223.546l4.23 4.23a.77.77 0 0 0 .252.167Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 446 B |
@@ -1,8 +1,14 @@
|
||||
import { getAddress } from 'viem';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
export default function getCheckedSummedAddress(address: string): string {
|
||||
try {
|
||||
return getAddress(address);
|
||||
return getAddress(
|
||||
address,
|
||||
// We need to pass chainId to getAddress to make it work correctly for some chains, e.g. Rootstock
|
||||
config.chain.id ? Number(config.chain.id) : undefined,
|
||||
);
|
||||
} catch (error) {
|
||||
return address;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import _omit from 'lodash/omit';
|
||||
import _pickBy from 'lodash/pickBy';
|
||||
import { omit, pickBy } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import type { CsrfData } from 'types/client/account';
|
||||
@@ -38,7 +37,7 @@ export default function useApiFetch() {
|
||||
const resource: ApiResource = RESOURCES[resourceName];
|
||||
const url = buildUrl(resourceName, pathParams, queryParams);
|
||||
const withBody = isBodyAllowed(fetchParams?.method);
|
||||
const headers = _pickBy({
|
||||
const headers = pickBy({
|
||||
'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined,
|
||||
Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined,
|
||||
'x-csrf-token': withBody && csrfToken ? csrfToken : undefined,
|
||||
@@ -55,7 +54,7 @@ export default function useApiFetch() {
|
||||
// change condition here if something is changed
|
||||
credentials: config.features.account.isEnabled ? 'include' : 'same-origin',
|
||||
headers,
|
||||
..._omit(fetchParams, 'headers'),
|
||||
...(fetchParams ? omit(fetchParams, [ 'headers' ]) : {}),
|
||||
},
|
||||
{
|
||||
resource: resource.path,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import clamp from 'lodash/clamp';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { throttle, clamp } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const ABSENT_PARAM_ERROR_MESSAGE = 'Required param not provided';
|
||||
|
||||
export default function throwOnAbsentParamError(param: unknown) {
|
||||
if (!param) {
|
||||
throw new Error('Required param not provided', { cause: { status: 404 } });
|
||||
throw new Error(ABSENT_PARAM_ERROR_MESSAGE, { cause: { status: 404 } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _debounce from 'lodash/debounce';
|
||||
import { debounce } from 'es-toolkit';
|
||||
import type { LegacyRef } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function useClientRect<E extends Element>(): [ DOMRect | null, Le
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeHandler = _debounce(() => {
|
||||
const resizeHandler = debounce(() => {
|
||||
setRect(nodeRef.current?.getBoundingClientRect() ?? null);
|
||||
}, 100);
|
||||
|
||||
|
||||
@@ -5,11 +5,9 @@ import isNeedProxy from 'lib/api/isNeedProxy';
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import * as cookies from 'lib/cookies';
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import { useRollbar } from 'lib/rollbar';
|
||||
|
||||
export default function useGetCsrfToken() {
|
||||
const nodeApiFetch = useFetch();
|
||||
const rollbar = useRollbar();
|
||||
|
||||
return useQuery({
|
||||
queryKey: getResourceKey('csrf'),
|
||||
@@ -20,11 +18,13 @@ export default function useGetCsrfToken() {
|
||||
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
|
||||
|
||||
if (!csrfFromHeader) {
|
||||
rollbar?.warn('Client fetch failed', {
|
||||
resource: 'csrf',
|
||||
status_code: 500,
|
||||
status_text: 'Unable to obtain csrf token from header',
|
||||
});
|
||||
// I am not sure should we log this error or not
|
||||
// so I commented it out for now
|
||||
// rollbar?.warn('Client fetch failed', {
|
||||
// resource: 'csrf',
|
||||
// status_code: 500,
|
||||
// status_text: 'Unable to obtain csrf token from header',
|
||||
// });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import throttle from 'lodash/throttle';
|
||||
import { throttle } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
export default function useIsSticky(ref: React.RefObject<HTMLDivElement>, offset = 0, isEnabled = true) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _clamp from 'lodash/clamp';
|
||||
import { clamp } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function useLazyRenderedList(list: Array<unknown>, isEnabled: boo
|
||||
|
||||
React.useEffect(() => {
|
||||
if (inView) {
|
||||
setRenderedItemsNum((prev) => _clamp(prev + STEP, 0, list.length));
|
||||
setRenderedItemsNum((prev) => clamp(prev + STEP, 0, list.length));
|
||||
}
|
||||
}, [ inView, list.length ]);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ exports[`generates correct metadata for: static route 1`] = `
|
||||
"description": "Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the Blockscout (Blockscout) Explorer network.",
|
||||
"opengraph": {
|
||||
"description": "",
|
||||
"imageUrl": "http://localhost:3000/static/og_placeholder.png",
|
||||
"imageUrl": "http://localhost:3000/static/og_image.png",
|
||||
"title": "Blockscout transactions - Blockscout explorer | Blockscout",
|
||||
},
|
||||
"title": "Blockscout transactions - Blockscout explorer | Blockscout",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _capitalize from 'lodash/capitalize';
|
||||
import { capitalize } from 'es-toolkit';
|
||||
|
||||
export default function getTabName(tab: string) {
|
||||
return tab !== '' ? _capitalize(tab.replaceAll('_', ' ')) : 'Default';
|
||||
return tab !== '' ? capitalize(tab.replaceAll('_', ' ')) : 'Default';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _capitalize from 'lodash/capitalize';
|
||||
import { capitalize } from 'es-toolkit';
|
||||
import type { Config } from 'mixpanel-browser';
|
||||
import mixpanel from 'mixpanel-browser';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -40,12 +40,12 @@ export default function useMixpanelInit() {
|
||||
'Viewport width': window.innerWidth,
|
||||
'Viewport height': window.innerHeight,
|
||||
Language: window.navigator.language,
|
||||
'Device type': _capitalize(deviceType),
|
||||
'Device type': capitalize(deviceType),
|
||||
'User id': userId,
|
||||
});
|
||||
mixpanel.identify(userId);
|
||||
userProfile.set({
|
||||
'Device Type': _capitalize(deviceType),
|
||||
'Device Type': capitalize(deviceType),
|
||||
...(isAuth ? { 'With Account': true } : {}),
|
||||
});
|
||||
userProfile.setOnce({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import _compose from 'lodash/fp/compose';
|
||||
import _mapValues from 'lodash/mapValues';
|
||||
import { mapValues } from 'es-toolkit';
|
||||
|
||||
import type { NetworkExplorer } from 'types/networks';
|
||||
|
||||
@@ -32,7 +31,7 @@ const networkExplorers: Array<NetworkExplorer> = (() => {
|
||||
return config.UI.explorers.items.map((explorer) => ({
|
||||
...explorer,
|
||||
baseUrl: stripTrailingSlash(explorer.baseUrl),
|
||||
paths: _mapValues(explorer.paths, _compose(stripTrailingSlash, addLeadingSlash)),
|
||||
paths: mapValues(explorer.paths, (value) => value ? stripTrailingSlash(addLeadingSlash(value)) : value),
|
||||
}));
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _uniq from 'lodash/uniq';
|
||||
import { uniq } from 'es-toolkit';
|
||||
|
||||
import isBrowser from './isBrowser';
|
||||
|
||||
@@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) {
|
||||
}
|
||||
|
||||
const keywordsArr = getRecentSearchKeywords();
|
||||
const result = _uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1);
|
||||
const result = uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1);
|
||||
window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ import type React from 'react';
|
||||
import type { Configuration } from 'rollbar';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { ABSENT_PARAM_ERROR_MESSAGE } from 'lib/errors/throwOnAbsentParamError';
|
||||
import { RESOURCE_LOAD_ERROR_MESSAGE } from 'lib/errors/throwOnResourceLoadError';
|
||||
|
||||
import { isBot, isHeadlessBrowser, isNextJsChunkError, getRequestInfo } from './utils';
|
||||
|
||||
const feature = config.features.rollbar;
|
||||
|
||||
@@ -20,4 +24,42 @@ export const clientConfig: Configuration | undefined = feature.isEnabled ? {
|
||||
code_version: feature.codeVersion,
|
||||
app_instance: feature.instance,
|
||||
},
|
||||
checkIgnore(isUncaught, args, item) {
|
||||
if (isBot(window.navigator.userAgent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isHeadlessBrowser(window.navigator.userAgent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNextJsChunkError(getRequestInfo(item)?.url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
hostSafeList: [ config.app.host ].filter(Boolean),
|
||||
ignoredMessages: [
|
||||
// these are React errors - "NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node."
|
||||
// they could be caused by browser extensions
|
||||
// one of the examples - https://github.com/facebook/react/issues/11538
|
||||
// we can ignore them for now
|
||||
'NotFoundError',
|
||||
|
||||
// these are errors that we throw on when make a call to the API
|
||||
RESOURCE_LOAD_ERROR_MESSAGE,
|
||||
ABSENT_PARAM_ERROR_MESSAGE,
|
||||
|
||||
// Filter out network-related errors that are usually not actionable
|
||||
'Network Error',
|
||||
'Failed to fetch',
|
||||
|
||||
// Filter out CORS errors from third-party extensions
|
||||
'cross-origin',
|
||||
|
||||
// Filter out client-side navigation cancellations
|
||||
'cancelled navigation',
|
||||
],
|
||||
maxItems: 10, // Max items per page load
|
||||
} : undefined;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Dictionary } from 'rollbar';
|
||||
|
||||
export function isBot(userAgent: string | undefined) {
|
||||
if (!userAgent) return false;
|
||||
|
||||
const botPatterns = [
|
||||
'Googlebot', // Google
|
||||
'Baiduspider', // Baidu
|
||||
'bingbot', // Bing
|
||||
'YandexBot', // Yandex
|
||||
'DuckDuckBot', // DuckDuckGo
|
||||
'Slurp', // Yahoo
|
||||
'Applebot', // Apple
|
||||
'facebookexternalhit', // Facebook
|
||||
'Twitterbot', // Twitter
|
||||
'rogerbot', // Moz
|
||||
'Alexa', // Alexa
|
||||
'AhrefsBot', // Ahrefs
|
||||
'SemrushBot', // Semrush
|
||||
'spider', // Generic spiders
|
||||
'crawler', // Generic crawlers
|
||||
];
|
||||
|
||||
return botPatterns.some(pattern =>
|
||||
userAgent.toLowerCase().includes(pattern.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
export function isHeadlessBrowser(userAgent: string | undefined) {
|
||||
if (!userAgent) return false;
|
||||
|
||||
if (
|
||||
userAgent.includes('headless') ||
|
||||
userAgent.includes('phantomjs') ||
|
||||
userAgent.includes('selenium') ||
|
||||
userAgent.includes('puppeteer')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function isNextJsChunkError(url: unknown) {
|
||||
if (typeof url !== 'string') return false;
|
||||
return url.includes('/_next/');
|
||||
}
|
||||
|
||||
export function getRequestInfo(item: Dictionary): { url: string } | undefined {
|
||||
if (
|
||||
!item.request ||
|
||||
item.request === null ||
|
||||
typeof item.request !== 'object' ||
|
||||
!('url' in item.request) ||
|
||||
typeof item.request.url !== 'string'
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return { url: item.request.url };
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import _upperFirst from 'lodash/upperFirst';
|
||||
import { upperFirst } from 'es-toolkit';
|
||||
|
||||
import type { Metadata, MetadataAttributes } from 'types/client/token';
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function attributesParser(attributes: Array<unknown>): Metadata['
|
||||
|
||||
return {
|
||||
...formatValue(value, display, trait),
|
||||
trait_type: _upperFirst(trait || 'property'),
|
||||
trait_type: upperFirst(trait || 'property'),
|
||||
};
|
||||
})
|
||||
.filter((item) => item?.value)
|
||||
|
||||
@@ -12,7 +12,7 @@ const currentChain = {
|
||||
},
|
||||
rpcUrls: {
|
||||
'default': {
|
||||
http: [ config.chain.rpcUrl ?? '' ],
|
||||
http: config.chain.rpcUrls,
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _get from 'lodash/get';
|
||||
import { get } from 'es-toolkit/compat';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
@@ -25,7 +25,7 @@ export default function useAddOrSwitchChain() {
|
||||
|
||||
const errorObj = getErrorObj(error);
|
||||
const code = errorObj && 'code' in errorObj ? errorObj.code : undefined;
|
||||
const originalErrorCode = _get(errorObj, 'data.originalError.code');
|
||||
const originalErrorCode = get(errorObj, 'data.originalError.code');
|
||||
|
||||
// This error code indicates that the chain has not been added to Wallet.
|
||||
if (code === 4902 || originalErrorCode === 4902) {
|
||||
@@ -37,7 +37,7 @@ export default function useAddOrSwitchChain() {
|
||||
symbol: config.chain.currency.symbol,
|
||||
decimals: config.chain.currency.decimals,
|
||||
},
|
||||
rpcUrls: [ config.chain.rpcUrl ],
|
||||
rpcUrls: config.chain.rpcUrls,
|
||||
blockExplorerUrls: [ config.app.baseUrl ],
|
||||
} ] as never;
|
||||
// in wagmi types for wallet_addEthereumChain method is not provided
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi';
|
||||
import { http } from 'viem';
|
||||
import { fallback, http } from 'viem';
|
||||
import { createConfig } from 'wagmi';
|
||||
|
||||
import config from 'configs/app';
|
||||
@@ -13,7 +13,11 @@ const wagmi = (() => {
|
||||
const wagmiConfig = createConfig({
|
||||
chains: [ currentChain ],
|
||||
transports: {
|
||||
[currentChain.id]: http(config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc`),
|
||||
[currentChain.id]: fallback(
|
||||
config.chain.rpcUrls
|
||||
.map((url) => http(url))
|
||||
.concat(http(`${ config.api.endpoint }/api/eth-rpc`)),
|
||||
),
|
||||
},
|
||||
ssr: true,
|
||||
batch: { multicall: { wait: 100 } },
|
||||
@@ -26,7 +30,7 @@ const wagmi = (() => {
|
||||
networks: chains,
|
||||
multiInjectedProviderDiscovery: true,
|
||||
transports: {
|
||||
[currentChain.id]: http(),
|
||||
[currentChain.id]: fallback(config.chain.rpcUrls.map((url) => http(url))),
|
||||
},
|
||||
projectId: feature.walletConnect.projectId,
|
||||
ssr: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _padStart from 'lodash/padStart';
|
||||
import { padStart } from 'es-toolkit/compat';
|
||||
|
||||
import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block';
|
||||
|
||||
@@ -42,11 +42,11 @@ function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails {
|
||||
amount: `${ 100 - index }210001063118670575`,
|
||||
account: {
|
||||
...addressMock.withoutName,
|
||||
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ _padStart(String(index), 2, '0') }`,
|
||||
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`,
|
||||
},
|
||||
associated_account: {
|
||||
...addressMock.withoutName,
|
||||
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ _padStart(String(index), 2, '0') }`,
|
||||
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _mapValues from 'lodash/mapValues';
|
||||
import { mapValues } from 'es-toolkit';
|
||||
|
||||
import type { HomeStats } from 'types/api/stats';
|
||||
|
||||
@@ -51,17 +51,17 @@ export const withBtcLocked: HomeStats = {
|
||||
|
||||
export const withoutFiatPrices: HomeStats = {
|
||||
...base,
|
||||
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null),
|
||||
gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null) : null,
|
||||
};
|
||||
|
||||
export const withoutGweiPrices: HomeStats = {
|
||||
...base,
|
||||
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null),
|
||||
gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null) : null,
|
||||
};
|
||||
|
||||
export const withoutBothPrices: HomeStats = {
|
||||
...base,
|
||||
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null),
|
||||
gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null) : null,
|
||||
};
|
||||
|
||||
export const withoutGasInfo: HomeStats = {
|
||||
|
||||
@@ -51,7 +51,7 @@ export function app(): CspDev.DirectiveDescriptor {
|
||||
getFeaturePayload(config.features.rewards)?.api.endpoint,
|
||||
|
||||
// chain RPC server
|
||||
config.chain.rpcUrl,
|
||||
...config.chain.rpcUrls,
|
||||
'https://infragrid.v.network', // RPC providers
|
||||
|
||||
// github (spec for api-docs page)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type CspDev from 'csp-dev';
|
||||
import { uniq } from 'es-toolkit';
|
||||
|
||||
export const KEY_WORDS = {
|
||||
BLOB: 'blob:',
|
||||
@@ -11,17 +12,6 @@ export const KEY_WORDS = {
|
||||
UNSAFE_EVAL: '\'unsafe-eval\'',
|
||||
};
|
||||
|
||||
// we cannot use lodash/uniq and lodash/mergeWith in middleware code since it calls new Set() and it'is causing an error in Next.js
|
||||
// "Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime"
|
||||
export function unique(array: Array<string | undefined>) {
|
||||
const set: Record<string, boolean> = {};
|
||||
for (const item of array) {
|
||||
item && (set[item] = true);
|
||||
}
|
||||
|
||||
return Object.keys(set);
|
||||
}
|
||||
|
||||
export function mergeDescriptors(...descriptors: Array<CspDev.DirectiveDescriptor>) {
|
||||
return descriptors.reduce((result, item) => {
|
||||
for (const _key in item) {
|
||||
@@ -50,7 +40,7 @@ export function makePolicyString(policyDescriptor: CspDev.DirectiveDescriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueValues = unique(value);
|
||||
const uniqueValues = uniq(value);
|
||||
return [ key, uniqueValues.join(' ') ].join(' ');
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pick } from 'es-toolkit';
|
||||
import type { IncomingMessage } from 'http';
|
||||
import _pick from 'lodash/pick';
|
||||
import type { NextApiRequest } from 'next';
|
||||
import type { NextApiRequestCookies } from 'next/dist/server/api-utils';
|
||||
import type { RequestInit, Response } from 'node-fetch';
|
||||
@@ -21,7 +21,7 @@ export default function fetchFactory(
|
||||
accept: _req.headers['accept'] || 'application/json',
|
||||
'content-type': _req.headers['content-type'] || 'application/json',
|
||||
cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '',
|
||||
..._pick(_req.headers, [
|
||||
...pick(_req.headers, [
|
||||
'x-csrf-token',
|
||||
'Authorization', // the old value, just in case
|
||||
'authorization', // Node.js automatically lowercases headers
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
"test:jest": "jest",
|
||||
"test:jest:watch": "jest --watch",
|
||||
"favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh",
|
||||
"og-image:generate:dev": "./tools/scripts/og-image-generator.dev.sh",
|
||||
"sitemap:generate:dev": "./tools/scripts/sitemap-generator.dev.sh",
|
||||
"monitoring:prometheus:local": "docker run --name blockscout_prometheus -d -p 127.0.0.1:9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus",
|
||||
"monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise"
|
||||
},
|
||||
@@ -80,6 +82,7 @@
|
||||
"dappscout-iframe": "0.2.5",
|
||||
"dayjs": "^1.11.5",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"es-toolkit": "1.31.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"getit-sdk": "^1.0.4",
|
||||
"gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
|
||||
@@ -87,7 +90,6 @@
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-ws": "^5.11.3",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash": "^4.0.0",
|
||||
"magic-bytes.js": "1.8.0",
|
||||
"mixpanel-browser": "^2.47.0",
|
||||
"monaco-editor": "^0.34.1",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import _pick from 'lodash/pick';
|
||||
import _pickBy from 'lodash/pickBy';
|
||||
import { pick, pickBy } from 'es-toolkit';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import fetchFactory from 'nextjs/utils/fetchProxy';
|
||||
@@ -18,7 +17,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => {
|
||||
);
|
||||
const apiRes = await fetchFactory(nextReq)(
|
||||
url.toString(),
|
||||
_pickBy(_pick(nextReq, [ 'body', 'method' ]), Boolean),
|
||||
pickBy(pick(nextReq, [ 'body', 'method' ]), Boolean),
|
||||
);
|
||||
|
||||
// proxy some headers from API
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TestFixture, Page } from '@playwright/test';
|
||||
import _isEqual from 'lodash/isEqual';
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import { encodeFunctionData, encodeFunctionResult, type AbiFunction } from 'viem';
|
||||
|
||||
import { getEnvValue } from 'configs/app/utils';
|
||||
@@ -43,7 +43,7 @@ const fixture: TestFixture<MockContractReadResponseFixture, { page: Page }> = as
|
||||
value: params?.value,
|
||||
};
|
||||
|
||||
if (_isEqual(params, callParams) && id) {
|
||||
if (isEqual(params, callParams) && id) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
json: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TestFixture, Page } from '@playwright/test';
|
||||
import _isEqual from 'lodash/isEqual';
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import type { PublicRpcSchema } from 'viem';
|
||||
|
||||
import { getEnvValue } from 'configs/app/utils';
|
||||
@@ -34,7 +34,7 @@ const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ pag
|
||||
...(rpcMock.Parameters ? { params: rpcMock.Parameters } : {}),
|
||||
};
|
||||
|
||||
if (_isEqual(json, payload) && id !== undefined) {
|
||||
if (isEqual(json, payload) && id !== undefined) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
json: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import './fonts.css';
|
||||
import './index.css';
|
||||
import { beforeMount } from '@playwright/experimental-ct-react/hooks';
|
||||
import _defaultsDeep from 'lodash/defaultsDeep';
|
||||
import MockDate from 'mockdate';
|
||||
import * as router from 'next/router';
|
||||
|
||||
@@ -12,12 +11,15 @@ const NEXT_ROUTER_MOCK = {
|
||||
replace: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
beforeMount(async({ hooksConfig }) => {
|
||||
beforeMount(async({ hooksConfig }: { hooksConfig?: { router: typeof router } }) => {
|
||||
// Before mount, redefine useRouter to return mock value from test.
|
||||
|
||||
// @ts-ignore: I really want to redefine this property :)
|
||||
// eslint-disable-next-line no-import-assign
|
||||
router.useRouter = () => _defaultsDeep(hooksConfig?.router, NEXT_ROUTER_MOCK);
|
||||
router.useRouter = () => ({
|
||||
...NEXT_ROUTER_MOCK,
|
||||
...hooksConfig?.router,
|
||||
});
|
||||
|
||||
// set current date
|
||||
MockDate.set('2022-11-11T12:00:00Z');
|
||||
|
||||
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Skeleton as SkeletonComponent } from '@chakra-ui/react';
|
||||
import {
|
||||
defineStyle,
|
||||
|
||||
@@ -41,6 +41,7 @@ const LOCAL_ENVS = {
|
||||
const IGNORED_ENVS = [
|
||||
'NEXT_PUBLIC_GIT_COMMIT_SHA',
|
||||
'NEXT_PUBLIC_GIT_TAG',
|
||||
'NEXT_PUBLIC_ICON_SPRITE_HASH',
|
||||
];
|
||||
|
||||
function parseScriptArgs() {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# use this script for testing the og image generator
|
||||
|
||||
config_file="./configs/envs/.env.zkevm"
|
||||
|
||||
dotenv \
|
||||
-e $config_file \
|
||||
-- bash -c 'node ./deploy/scripts/og_image_generator.js'
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
config_file="./configs/envs/.env.eth"
|
||||
|
||||
if [ ! -f "$config_file" ]; then
|
||||
echo "Error: File '$config_file' not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotenv \
|
||||
-e $config_file \
|
||||
-- bash -c 'cd ./deploy/tools/sitemap-generator && yarn && yarn next-sitemap'
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as bens from '@blockscout/bens-types';
|
||||
import type { TokenType } from 'types/api/token';
|
||||
|
||||
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
|
||||
@@ -47,6 +48,7 @@ export interface SearchResultDomain {
|
||||
expiry_date?: string;
|
||||
name: string;
|
||||
names_count: number;
|
||||
protocol?: bens.ProtocolInfo;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { chakra, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react';
|
||||
import { chakra, Tooltip, Hide, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { CsvExportParams } from 'types/client/address';
|
||||
@@ -8,6 +8,7 @@ import { route } from 'nextjs-routes';
|
||||
import config from 'configs/app';
|
||||
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Box, Flex, Skeleton, Text } from '@chakra-ui/react';
|
||||
import { Box, Flex, Text } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import type { NovesResponseData } from 'types/api/noves';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Td, Tr, Skeleton, Box } from '@chakra-ui/react';
|
||||
import { Td, Tr, Box } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import type { NovesResponseData } from 'types/api/noves';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import NovesFromTo from 'ui/shared/Noves/NovesFromTo';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Text, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { Text, Flex } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
@@ -8,6 +8,7 @@ import config from 'configs/app';
|
||||
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
|
||||
import { currencyUnits } from 'lib/units';
|
||||
import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Td, Tr, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { Td, Tr, Flex } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { Block } from 'types/api/block';
|
||||
import config from 'configs/app';
|
||||
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
|
||||
import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Text, Stat, StatHelpText, StatArrow, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
|
||||
|
||||
import { WEI, ZERO } from 'lib/consts';
|
||||
import { currencyUnits } from 'lib/units';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Td, Tr, Text, Stat, StatHelpText, StatArrow, Skeleton } from '@chakra-ui/react';
|
||||
import { Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
|
||||
|
||||
import { WEI, ZERO } from 'lib/consts';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
Image,
|
||||
Skeleton,
|
||||
useDisclosure,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
@@ -14,6 +13,7 @@ import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import Popover from 'ui/shared/chakra/Popover';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkExternal from 'ui/shared/links/LinkExternal';
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Button, Skeleton } from '@chakra-ui/react';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean;
|
||||
addressHash: string;
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Skeleton,
|
||||
StackDivider,
|
||||
useDisclosure,
|
||||
VStack,
|
||||
@@ -22,6 +21,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import { apos } from 'lib/html-entities';
|
||||
import Popover from 'ui/shared/chakra/Popover';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { chakra, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { chakra, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import LinkNewTab from 'ui/shared/links/LinkNewTab';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, Skeleton, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { Flex, Text, Tooltip } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { SmartContract } from 'types/api/contract';
|
||||
@@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract';
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import formatLanguageName from 'lib/contracts/formatLanguageName';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
import CodeEditor from 'ui/shared/monaco/CodeEditor';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { chakra, Alert, Box, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { chakra, Alert, Box, Flex } from '@chakra-ui/react';
|
||||
import type { Channel } from 'phoenix';
|
||||
import React from 'react';
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract';
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import useSocketMessage from 'lib/socket/useSocketMessage';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import LinkExternal from 'ui/shared/links/LinkExternal';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import noop from 'lodash/noop';
|
||||
import { noop } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { chakra, useColorModeValue, Flex, GridItem, Skeleton } from '@chakra-ui/react';
|
||||
import { chakra, useColorModeValue, Flex, GridItem } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import Hint from 'ui/shared/Hint';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Accordion, Box, Flex, Link } from '@chakra-ui/react';
|
||||
import _range from 'lodash/range';
|
||||
import { range } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import type { SmartContractMethod } from './types';
|
||||
@@ -39,7 +39,7 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro
|
||||
}
|
||||
|
||||
if (expandedSections.length < abi.length) {
|
||||
setExpandedSections(_range(0, abi.length));
|
||||
setExpandedSections(range(0, abi.length));
|
||||
} else {
|
||||
setExpandedSections([]);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Alert, Button, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { Alert, Button, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import useWeb3Wallet from 'lib/web3/useWallet';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Alert, Skeleton } from '@chakra-ui/react';
|
||||
import { Alert } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
interface Props {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Flex, Skeleton, useDisclosure } from '@chakra-ui/react';
|
||||
import { Button, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
@@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract';
|
||||
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import RawDataSnippet from 'ui/shared/RawDataSnippet';
|
||||
import AuthGuard from 'ui/snippets/auth/AuthGuard';
|
||||
import useIsAuth from 'ui/snippets/auth/useIsAuth';
|
||||
|
||||
@@ -28,7 +28,9 @@ interface Props {
|
||||
}
|
||||
|
||||
const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDisabled, isOptional: isOptionalProp, level }: Props) => {
|
||||
const ref = React.useRef<HTMLInputElement>(null);
|
||||
const ref = React.useRef<HTMLInputElement>();
|
||||
|
||||
const [ intPower, setIntPower ] = React.useState<number>(18);
|
||||
|
||||
const isNativeCoin = data.fieldType === 'native_coin';
|
||||
const isOptional = isOptionalProp || isNativeCoin;
|
||||
@@ -46,6 +48,8 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
|
||||
|
||||
const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64;
|
||||
|
||||
React.useImperativeHandle(field.ref, () => ref.current);
|
||||
|
||||
const handleChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const formattedValue = format(event.target.value);
|
||||
field.onChange(formattedValue); // data send back to hook form
|
||||
@@ -83,6 +87,42 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
|
||||
setValue(name, newValue, { shouldValidate: true });
|
||||
}, [ format, name, setValue ]);
|
||||
|
||||
const handlePaste = React.useCallback((event: React.ClipboardEvent<HTMLInputElement>) => {
|
||||
if (!argTypeMatchInt || !hasMultiplyButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = Number(event.clipboardData.getData('text'));
|
||||
|
||||
if (Object.is(value, NaN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFloat = Number.isFinite(value) && !Number.isInteger(value);
|
||||
|
||||
if (!isFloat) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (field.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = value * 10 ** intPower;
|
||||
const formattedValue = format(newValue.toString());
|
||||
|
||||
field.onChange(formattedValue);
|
||||
setValue(name, formattedValue, { shouldValidate: true });
|
||||
window.setTimeout(() => {
|
||||
// move cursor to the end of the input
|
||||
// but we have to wait for the input to get the new value
|
||||
const END_OF_INPUT = 100;
|
||||
ref.current?.setSelectionRange(END_OF_INPUT, END_OF_INPUT);
|
||||
}, 100);
|
||||
}, [ argTypeMatchInt, hasMultiplyButton, intPower, format, field, setValue, name ]);
|
||||
|
||||
const error = fieldState.error;
|
||||
|
||||
return (
|
||||
@@ -107,9 +147,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
|
||||
thousandSeparator: ' ',
|
||||
decimalScale: 0,
|
||||
allowNegative: !argTypeMatchInt.isUnsigned,
|
||||
getInputRef: (element: HTMLInputElement) => {
|
||||
ref.current = element;
|
||||
},
|
||||
} : {}) }
|
||||
ref={ ref }
|
||||
// as we use mutable ref, we have to cast it to React.LegacyRef<HTMLInputElement> to trick chakra and typescript
|
||||
ref={ ref as React.LegacyRef<HTMLInputElement> | undefined }
|
||||
onChange={ handleChange }
|
||||
onPaste={ handlePaste }
|
||||
required={ !isOptional }
|
||||
isInvalid={ Boolean(error) }
|
||||
placeholder={ data.type }
|
||||
@@ -148,7 +193,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
|
||||
Max
|
||||
</Button>
|
||||
)) }
|
||||
{ hasMultiplyButton && <ContractMethodMultiplyButton onClick={ handleMultiplyButtonClick } isDisabled={ isDisabled }/> }
|
||||
{ hasMultiplyButton && (
|
||||
<ContractMethodMultiplyButton
|
||||
onClick={ handleMultiplyButtonClick }
|
||||
isDisabled={ isDisabled }
|
||||
initialValue={ intPower }
|
||||
onChange={ setIntPower }
|
||||
/>
|
||||
) }
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
{ error && <Box color="error" fontSize="sm" lineHeight={ 5 } mt={ 1 }>{ error.message }</Box> }
|
||||
|
||||
@@ -20,10 +20,12 @@ import IconSvg from 'ui/shared/IconSvg';
|
||||
interface Props {
|
||||
onClick: (power: number) => void;
|
||||
isDisabled?: boolean;
|
||||
initialValue: number;
|
||||
onChange: (power: number) => void;
|
||||
}
|
||||
|
||||
const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => {
|
||||
const [ selectedOption, setSelectedOption ] = React.useState<number | undefined>(18);
|
||||
const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onChange }: Props) => {
|
||||
const [ selectedOption, setSelectedOption ] = React.useState<number | undefined>(initialValue);
|
||||
const [ customValue, setCustomValue ] = React.useState<number>();
|
||||
const { isOpen, onToggle, onClose } = useDisclosure();
|
||||
|
||||
@@ -35,13 +37,16 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => {
|
||||
setSelectedOption((prev) => prev === id ? undefined : id);
|
||||
setCustomValue(undefined);
|
||||
onClose();
|
||||
onChange(id);
|
||||
}
|
||||
}, [ onClose ]);
|
||||
}, [ onClose, onChange ]);
|
||||
|
||||
const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCustomValue(Number(event.target.value));
|
||||
const value = Number(event.target.value);
|
||||
setCustomValue(value);
|
||||
setSelectedOption(undefined);
|
||||
}, []);
|
||||
onChange(value);
|
||||
}, [ onChange ]);
|
||||
|
||||
const value = selectedOption || customValue;
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 217 KiB |
@@ -1,4 +1,4 @@
|
||||
import _set from 'lodash/set';
|
||||
import { set } from 'es-toolkit/compat';
|
||||
|
||||
import type { ContractAbiItemInput } from '../types';
|
||||
|
||||
@@ -78,7 +78,7 @@ export function transformFormDataToMethodArgs(formData: ContractMethodFormFields
|
||||
|
||||
for (const field in formData) {
|
||||
const value = formData[field];
|
||||
_set(result, field.replaceAll(':', '.'), value);
|
||||
set(result, field.replaceAll(':', '.'), value);
|
||||
}
|
||||
|
||||
const filteredResult = filterOutEmptyItems(result);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _pickBy from 'lodash/pickBy';
|
||||
import { pickBy } from 'es-toolkit';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function useMethodsFilters({ abi }: Params) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryForPathname = _pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
|
||||
const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
|
||||
router.push(
|
||||
{ pathname: router.pathname, query: { ...queryForPathname, tab: nextTab } },
|
||||
undefined,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Skeleton } from '@chakra-ui/react';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
@@ -8,6 +7,7 @@ import type { AddressCounters } from 'types/api/address';
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Image, Tooltip } from '@chakra-ui/react';
|
||||
import _capitalize from 'lodash/capitalize';
|
||||
import { capitalize } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import type { MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig';
|
||||
@@ -25,10 +25,10 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider
|
||||
const buttonContent = hasSingleProvider ? (
|
||||
<>
|
||||
{ buttonIcon }
|
||||
{ _capitalize(item.name) }
|
||||
{ capitalize(item.name) }
|
||||
</>
|
||||
) : (
|
||||
<Tooltip label={ _capitalize(item.name) }>{ buttonIcon }</Tooltip>
|
||||
<Tooltip label={ capitalize(item.name) }>{ buttonIcon }</Tooltip>
|
||||
);
|
||||
|
||||
const linkProps = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { Address } from 'types/api/address';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Skeleton, Text, Flex } from '@chakra-ui/react';
|
||||
import { Text, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { Address } from 'types/api/address';
|
||||
@@ -6,6 +6,7 @@ import type { Address } from 'types/api/address';
|
||||
import config from 'configs/app';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
|
||||
import { getTokensTotalInfo } from '../utils/tokenUtils';
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
useDisclosure,
|
||||
Tooltip,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
} from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import QRCode from 'qrcode';
|
||||
@@ -23,6 +22,7 @@ import type { Address as AddressType } from 'types/api/address';
|
||||
import getPageType from 'lib/mixpanel/getPageType';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import { useRollbar } from 'lib/rollbar';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Image, Skeleton } from '@chakra-ui/react';
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import config from 'configs/app';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import LinkExternal from 'ui/shared/links/LinkExternal';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
|
||||
|
||||
@@ -8,12 +8,11 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Show,
|
||||
Skeleton,
|
||||
useDisclosure,
|
||||
chakra,
|
||||
} from '@chakra-ui/react';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import _clamp from 'lodash/clamp';
|
||||
import { clamp } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import type * as bens from '@blockscout/bens-types';
|
||||
@@ -23,6 +22,7 @@ import { route } from 'nextjs-routes';
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import Popover from 'ui/shared/chakra/Popover';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
@@ -37,7 +37,7 @@ interface Props {
|
||||
const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => {
|
||||
return (
|
||||
<Grid
|
||||
templateColumns={{ base: `repeat(${ _clamp(data.length, 1, 2) }, 1fr)`, lg: `repeat(${ _clamp(data.length, 1, 3) }, 1fr)` }}
|
||||
templateColumns={{ base: `repeat(${ clamp(data.length, 1, 2) }, 1fr)`, lg: `repeat(${ clamp(data.length, 1, 3) }, 1fr)` }}
|
||||
columnGap={ 8 }
|
||||
rowGap={ 4 }
|
||||
mt={ 2 }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressEpochRewardsItem } from 'types/api/address';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Flex, Td, Tr, Text, Skeleton } from '@chakra-ui/react';
|
||||
import { Flex, Td, Tr, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressEpochRewardsItem } from 'types/api/address';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, HStack, Skeleton } from '@chakra-ui/react';
|
||||
import { Flex, HStack } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
import config from 'configs/app';
|
||||
import { currencyUnits } from 'lib/units';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tr, Td, Box, Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { Tr, Td, Box, Flex } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Divider, Text, Skeleton, useBoolean, Flex, Link, VStack, chakra, Box, Grid, GridItem } from '@chakra-ui/react';
|
||||
import { Divider, Text, useBoolean, Flex, Link, VStack, chakra, Box, Grid, GridItem } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import HashStringShorten from 'ui/shared/HashStringShorten';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Td, Tr, Text, Skeleton, useBoolean, Link, Table, VStack, chakra } from '@chakra-ui/react';
|
||||
import { Td, Tr, Text, useBoolean, Link, Table, VStack, chakra } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import LinkInternal from 'ui/shared/links/LinkInternal';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, IconButton, Skeleton, Tooltip } from '@chakra-ui/react';
|
||||
import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react';
|
||||
import { useQueryClient, useIsFetching } from '@tanstack/react-query';
|
||||
import _sumBy from 'lodash/sumBy';
|
||||
import { sumBy } from 'es-toolkit';
|
||||
import NextLink from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
@@ -11,6 +11,7 @@ import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import useFetchTokens from '../utils/useFetchTokens';
|
||||
@@ -49,7 +50,7 @@ const TokenSelect = ({ onClick }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
const hasTokens = _sumBy(Object.values(data), ({ items }) => items.length) > 0;
|
||||
const hasTokens = sumBy(Object.values(data), ({ items }) => items.length) > 0;
|
||||
if (isError || !hasTokens) {
|
||||
return <Box py="6px">0</Box>;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Box, Button, Skeleton, chakra, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Box, Button, chakra, useColorModeValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { FormattedData } from './types';
|
||||
|
||||
import { space } from 'lib/html-entities';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import { getTokensTotalInfo } from '../utils/tokenUtils';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react';
|
||||
import _sumBy from 'lodash/sumBy';
|
||||
import { sumBy } from 'es-toolkit';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -26,7 +26,7 @@ interface Props {
|
||||
const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => {
|
||||
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
|
||||
|
||||
const hasFilteredResult = _sumBy(Object.values(filteredData), ({ items }) => items.length) > 0;
|
||||
const hasFilteredResult = sumBy(Object.values(filteredData), ({ items }) => items.length) > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||