Commit 06404103 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into chakra-v3

parents f5cdbc78 10881261
...@@ -7,3 +7,7 @@ NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx ...@@ -7,3 +7,7 @@ NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=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
\ No newline at end of file
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
/public/icons/sprite.svg /public/icons/sprite.svg
/public/icons/sprite.*.svg /public/icons/sprite.*.svg
/public/icons/README.md /public/icons/README.md
/public/static/og_image.png
/public/sitemap.xml
/public/robots.txt
/analyze /analyze
# production # production
......
...@@ -33,6 +33,12 @@ WORKDIR /favicon-generator ...@@ -33,6 +33,12 @@ WORKDIR /favicon-generator
COPY ./deploy/tools/favicon-generator/package.json ./deploy/tools/favicon-generator/yarn.lock ./ COPY ./deploy/tools/favicon-generator/package.json ./deploy/tools/favicon-generator/yarn.lock ./
RUN yarn --frozen-lockfile --network-timeout 100000 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 ******* # ****** STAGE 2: Build *******
...@@ -88,6 +94,12 @@ RUN cd ./deploy/tools/envs-validator && yarn build ...@@ -88,6 +94,12 @@ RUN cd ./deploy/tools/envs-validator && yarn build
# Copy dependencies and source code # Copy dependencies and source code
COPY --from=deps /favicon-generator/node_modules ./deploy/tools/favicon-generator/node_modules 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 ******** # ******* STAGE 3: Run ********
# ***************************** # *****************************
...@@ -122,11 +134,16 @@ COPY --chmod=755 ./deploy/scripts/validate_envs.sh . ...@@ -122,11 +134,16 @@ COPY --chmod=755 ./deploy/scripts/validate_envs.sh .
COPY --chmod=755 ./deploy/scripts/make_envs_script.sh . COPY --chmod=755 ./deploy/scripts/make_envs_script.sh .
## Assets downloader ## Assets downloader
COPY --chmod=755 ./deploy/scripts/download_assets.sh . COPY --chmod=755 ./deploy/scripts/download_assets.sh .
## OG image generator
COPY ./deploy/scripts/og_image_generator.js .
## Favicon generator ## Favicon generator
COPY --chmod=755 ./deploy/scripts/favicon_generator.sh . COPY --chmod=755 ./deploy/scripts/favicon_generator.sh .
COPY --from=builder /app/deploy/tools/favicon-generator ./deploy/tools/favicon-generator COPY --from=builder /app/deploy/tools/favicon-generator ./deploy/tools/favicon-generator
RUN ["chmod", "-R", "777", "./deploy/tools/favicon-generator"] RUN ["chmod", "-R", "777", "./deploy/tools/favicon-generator"]
RUN ["chmod", "-R", "777", "./public"] 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 ENVs files
COPY --from=builder /app/.env.registry . COPY --from=builder /app/.env.registry .
......
import type { RollupType } from 'types/client/rollup'; import type { RollupType } from 'types/client/rollup';
import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks'; 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; const DEFAULT_CURRENCY_DECIMALS = 18;
...@@ -17,6 +19,19 @@ const verificationType: NetworkVerificationType = (() => { ...@@ -17,6 +19,19 @@ const verificationType: NetworkVerificationType = (() => {
return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining'; 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({ const chain = Object.freeze({
id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'), id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'),
name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'), name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'),
...@@ -32,7 +47,7 @@ const chain = Object.freeze({ ...@@ -32,7 +47,7 @@ const chain = Object.freeze({
}, },
hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true', hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true',
tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC',
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), rpcUrls,
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
verificationType, verificationType,
}); });
......
...@@ -17,7 +17,7 @@ const config: Feature<{ walletConnect: { projectId: string } }> = (() => { ...@@ -17,7 +17,7 @@ const config: Feature<{ walletConnect: { projectId: string } }> = (() => {
chain.currency.name && chain.currency.name &&
chain.currency.symbol && chain.currency.symbol &&
chain.currency.decimals && chain.currency.decimals &&
chain.rpcUrl && chain.rpcUrls.length > 0 &&
walletConnectProjectId walletConnectProjectId
) { ) {
return Object.freeze({ return Object.freeze({
......
...@@ -33,7 +33,7 @@ const config: Feature<( ...@@ -33,7 +33,7 @@ const config: Feature<(
rating: { airtableApiKey: string; airtableBaseId: string } | undefined; rating: { airtableApiKey: string; airtableBaseId: string } | undefined;
graphLinksUrl: string | undefined; graphLinksUrl: string | undefined;
}> = (() => { }> = (() => {
if (enabled === 'true' && chain.rpcUrl && submitFormUrl) { if (enabled === 'true' && chain.rpcUrls.length > 0 && submitFormUrl) {
const props = { const props = {
submitFormUrl, submitFormUrl,
categoriesUrl, categoriesUrl,
......
...@@ -31,7 +31,7 @@ const config: Feature<{ ...@@ -31,7 +31,7 @@ const config: Feature<{
type, type,
L1BaseUrl: stripTrailingSlash(L1BaseUrl), L1BaseUrl: stripTrailingSlash(L1BaseUrl),
L2WithdrawalUrl: type === 'optimistic' ? L2WithdrawalUrl : undefined, 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, parentChainName: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME') : undefined,
homepage: { homepage: {
showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true',
......
import app from './app'; import app from './app';
import { getEnvValue, getExternalAssetFilePath } from './utils'; import { getEnvValue, getExternalAssetFilePath } from './utils';
const defaultImageUrl = '/static/og_placeholder.png'; const defaultImageUrl = '/static/og_image.png';
const meta = Object.freeze({ const meta = Object.freeze({
promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'false' ? false : true, promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'false' ? false : true,
......
# Set of ENVs for Shibarium network explorer # Set of ENVs for Shibarium network explorer
# https://www.shibariumscan.io # 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 # Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_PROTOCOL=http
...@@ -23,6 +23,7 @@ NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethere ...@@ -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_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_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_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_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_GRAPHIQL_TRANSACTION=0xce531d29c0c469fb00b443b8091b8c059b4f13d7e025dd0ef843401d02b9a1a9
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true
...@@ -31,15 +32,17 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] ...@@ -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_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_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://shibarium.us.auth0.com/v2/logout 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_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_ENABLED=true 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_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_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_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form 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_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_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=BONE NEXT_PUBLIC_NETWORK_CURRENCY_NAME=BONE
......
...@@ -13,23 +13,31 @@ NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws ...@@ -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_DESKTOP={ "id": "632019", "width": "728", "height": "90" }
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" }
NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler 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_BASE_PATH=/
NEXT_PUBLIC_API_HOST=zkevm.blockscout.com 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_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_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_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_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_GRAPHIQL_TRANSACTION=0x25fcb396fc8652dcd0040f677a1dcc6fecff390ecafc815894379a3f254f1aa9
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] 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_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_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_MARKETPLACE_ENABLED=false 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_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_METASUITES_ENABLED=true NEXT_PUBLIC_METASUITES_ENABLED=true
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC 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_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_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg
NEXT_PUBLIC_NETWORK_ID=1101 NEXT_PUBLIC_NETWORK_ID=1101
......
...@@ -35,6 +35,9 @@ export_envs_from_preset() { ...@@ -35,6 +35,9 @@ export_envs_from_preset() {
# If there is a preset, load the environment variables from the its file # If there is a preset, load the environment variables from the its file
export_envs_from_preset export_envs_from_preset
# Generate OG image
node --no-warnings ./og_image_generator.js
# Download external assets # Download external assets
./download_assets.sh ./public/assets/configs ./download_assets.sh ./public/assets/configs
...@@ -61,6 +64,9 @@ echo ...@@ -61,6 +64,9 @@ echo
# Create envs.js file with run-time environment variables for the client app # Create envs.js file with run-time environment variables for the client app
./make_envs_script.sh ./make_envs_script.sh
# Generate sitemap.xml and robots.txt files
./sitemap_generator.sh
# Print list of enabled features # Print list of enabled features
node ./feature-reporter.js node ./feature-reporter.js
......
/* 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.');
cd ./deploy/tools/sitemap-generator
yarn next-sitemap
\ No newline at end of file
...@@ -587,7 +587,21 @@ const schema = yup ...@@ -587,7 +587,21 @@ const schema = yup
NEXT_PUBLIC_NETWORK_NAME: yup.string().required(), NEXT_PUBLIC_NETWORK_NAME: yup.string().required(),
NEXT_PUBLIC_NETWORK_SHORT_NAME: yup.string(), NEXT_PUBLIC_NETWORK_SHORT_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_ID: yup.number().positive().integer().required(), 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_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(),
......
...@@ -6,3 +6,4 @@ NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32'] ...@@ -6,3 +6,4 @@ NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32']
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx 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']
/node_modules
\ No newline at end of file
/* 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)));
},
};
{
"name": "sitemap-generator",
"version": "1.0.0",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"next-sitemap": "4.2.3"
}
}
# 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/fs.scandir@2.1.5":
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/fs.stat@2.0.5", "@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==
next-sitemap@4.2.3:
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 ...@@ -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_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_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_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_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_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+ | | 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 ...@@ -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_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_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_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+ | | 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+ |
&nbsp; &nbsp;
......
...@@ -33,14 +33,9 @@ const RESTRICTED_MODULES = { ...@@ -33,14 +33,9 @@ const RESTRICTED_MODULES = {
], ],
message: 'Please use corresponding component or hook from ui/shared/chakra component instead', 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: [ patterns: [
'icons/*', 'icons/*',
'!lodash/*',
], ],
}; };
...@@ -432,6 +427,7 @@ export default tseslint.config( ...@@ -432,6 +427,7 @@ export default tseslint.config(
'pages/**', 'pages/**',
'nextjs/**', 'nextjs/**',
'playwright/**', 'playwright/**',
'deploy/scripts/**',
'deploy/tools/**', 'deploy/tools/**',
'middleware.ts', 'middleware.ts',
'instrumentation*.ts', 'instrumentation*.ts',
......
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <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> </svg>
import { getAddress } from 'viem'; import { getAddress } from 'viem';
import config from 'configs/app';
export default function getCheckedSummedAddress(address: string): string { export default function getCheckedSummedAddress(address: string): string {
try { 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) { } catch (error) {
return address; return address;
} }
......
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import _omit from 'lodash/omit'; import { omit, pickBy } from 'es-toolkit';
import _pickBy from 'lodash/pickBy';
import React from 'react'; import React from 'react';
import type { CsrfData } from 'types/client/account'; import type { CsrfData } from 'types/client/account';
...@@ -38,7 +37,7 @@ export default function useApiFetch() { ...@@ -38,7 +37,7 @@ export default function useApiFetch() {
const resource: ApiResource = RESOURCES[resourceName]; const resource: ApiResource = RESOURCES[resourceName];
const url = buildUrl(resourceName, pathParams, queryParams); const url = buildUrl(resourceName, pathParams, queryParams);
const withBody = isBodyAllowed(fetchParams?.method); const withBody = isBodyAllowed(fetchParams?.method);
const headers = _pickBy({ const headers = pickBy({
'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,
...@@ -55,7 +54,7 @@ export default function useApiFetch() { ...@@ -55,7 +54,7 @@ export default function useApiFetch() {
// change condition here if something is changed // change condition here if something is changed
credentials: config.features.account.isEnabled ? 'include' : 'same-origin', credentials: config.features.account.isEnabled ? 'include' : 'same-origin',
headers, headers,
..._omit(fetchParams, 'headers'), ...(fetchParams ? omit(fetchParams, [ 'headers' ]) : {}),
}, },
{ {
resource: resource.path, resource: resource.path,
......
import clamp from 'lodash/clamp'; import { throttle, clamp } from 'es-toolkit';
import throttle from 'lodash/throttle';
import React from 'react'; import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null); const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null);
......
export const ABSENT_PARAM_ERROR_MESSAGE = 'Required param not provided';
export default function throwOnAbsentParamError(param: unknown) { export default function throwOnAbsentParamError(param: unknown) {
if (!param) { if (!param) {
throw new Error('Required param not provided', { cause: { status: 404 } }); throw new Error(ABSENT_PARAM_ERROR_MESSAGE, { cause: { status: 404 } });
} }
} }
import _debounce from 'lodash/debounce'; import { debounce } from 'es-toolkit';
import type { LegacyRef } from 'react'; import type { LegacyRef } from 'react';
import React from 'react'; import React from 'react';
...@@ -19,7 +19,7 @@ export default function useClientRect<E extends Element>(): [ DOMRect | null, Le ...@@ -19,7 +19,7 @@ export default function useClientRect<E extends Element>(): [ DOMRect | null, Le
return; return;
} }
const resizeHandler = _debounce(() => { const resizeHandler = debounce(() => {
setRect(nodeRef.current?.getBoundingClientRect() ?? null); setRect(nodeRef.current?.getBoundingClientRect() ?? null);
}, 100); }, 100);
......
...@@ -5,11 +5,9 @@ import isNeedProxy from 'lib/api/isNeedProxy'; ...@@ -5,11 +5,9 @@ import isNeedProxy from 'lib/api/isNeedProxy';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import { useRollbar } from 'lib/rollbar';
export default function useGetCsrfToken() { export default function useGetCsrfToken() {
const nodeApiFetch = useFetch(); const nodeApiFetch = useFetch();
const rollbar = useRollbar();
return useQuery({ return useQuery({
queryKey: getResourceKey('csrf'), queryKey: getResourceKey('csrf'),
...@@ -20,11 +18,13 @@ export default function useGetCsrfToken() { ...@@ -20,11 +18,13 @@ export default function useGetCsrfToken() {
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf'); const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
if (!csrfFromHeader) { if (!csrfFromHeader) {
rollbar?.warn('Client fetch failed', { // I am not sure should we log this error or not
resource: 'csrf', // so I commented it out for now
status_code: 500, // rollbar?.warn('Client fetch failed', {
status_text: 'Unable to obtain csrf token from header', // resource: 'csrf',
}); // status_code: 500,
// status_text: 'Unable to obtain csrf token from header',
// });
return; return;
} }
......
import throttle from 'lodash/throttle'; import { throttle } from 'es-toolkit';
import React from 'react'; import React from 'react';
export default function useIsSticky(ref: React.RefObject<HTMLDivElement>, offset = 0, isEnabled = true) { export default function useIsSticky(ref: React.RefObject<HTMLDivElement>, offset = 0, isEnabled = true) {
......
import _clamp from 'lodash/clamp'; import { clamp } from 'es-toolkit';
import React from 'react'; import React from 'react';
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
...@@ -15,7 +15,7 @@ export default function useLazyRenderedList(list: Array<unknown>, isEnabled: boo ...@@ -15,7 +15,7 @@ export default function useLazyRenderedList(list: Array<unknown>, isEnabled: boo
React.useEffect(() => { React.useEffect(() => {
if (inView) { if (inView) {
setRenderedItemsNum((prev) => _clamp(prev + STEP, 0, list.length)); setRenderedItemsNum((prev) => clamp(prev + STEP, 0, list.length));
} }
}, [ inView, list.length ]); }, [ inView, list.length ]);
......
...@@ -32,7 +32,7 @@ exports[`generates correct metadata for: static route 1`] = ` ...@@ -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.", "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": { "opengraph": {
"description": "", "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",
}, },
"title": "Blockscout transactions - Blockscout explorer | Blockscout", "title": "Blockscout transactions - Blockscout explorer | Blockscout",
......
import _capitalize from 'lodash/capitalize'; import { capitalize } from 'es-toolkit';
export default function getTabName(tab: string) { export default function getTabName(tab: string) {
return tab !== '' ? _capitalize(tab.replaceAll('_', ' ')) : 'Default'; return tab !== '' ? capitalize(tab.replaceAll('_', ' ')) : 'Default';
} }
import _capitalize from 'lodash/capitalize'; import { capitalize } from 'es-toolkit';
import type { Config } from 'mixpanel-browser'; import type { Config } from 'mixpanel-browser';
import mixpanel from 'mixpanel-browser'; import mixpanel from 'mixpanel-browser';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
...@@ -40,12 +40,12 @@ export default function useMixpanelInit() { ...@@ -40,12 +40,12 @@ export default function useMixpanelInit() {
'Viewport width': window.innerWidth, 'Viewport width': window.innerWidth,
'Viewport height': window.innerHeight, 'Viewport height': window.innerHeight,
Language: window.navigator.language, Language: window.navigator.language,
'Device type': _capitalize(deviceType), 'Device type': capitalize(deviceType),
'User id': userId, 'User id': userId,
}); });
mixpanel.identify(userId); mixpanel.identify(userId);
userProfile.set({ userProfile.set({
'Device Type': _capitalize(deviceType), 'Device Type': capitalize(deviceType),
...(isAuth ? { 'With Account': true } : {}), ...(isAuth ? { 'With Account': true } : {}),
}); });
userProfile.setOnce({ userProfile.setOnce({
......
import _compose from 'lodash/fp/compose'; import { mapValues } from 'es-toolkit';
import _mapValues from 'lodash/mapValues';
import type { NetworkExplorer } from 'types/networks'; import type { NetworkExplorer } from 'types/networks';
...@@ -32,7 +31,7 @@ const networkExplorers: Array<NetworkExplorer> = (() => { ...@@ -32,7 +31,7 @@ const networkExplorers: Array<NetworkExplorer> = (() => {
return config.UI.explorers.items.map((explorer) => ({ return config.UI.explorers.items.map((explorer) => ({
...explorer, ...explorer,
baseUrl: stripTrailingSlash(explorer.baseUrl), baseUrl: stripTrailingSlash(explorer.baseUrl),
paths: _mapValues(explorer.paths, _compose(stripTrailingSlash, addLeadingSlash)), paths: mapValues(explorer.paths, (value) => value ? stripTrailingSlash(addLeadingSlash(value)) : value),
})); }));
})(); })();
......
import _uniq from 'lodash/uniq'; import { uniq } from 'es-toolkit';
import isBrowser from './isBrowser'; import isBrowser from './isBrowser';
...@@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) { ...@@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) {
} }
const keywordsArr = getRecentSearchKeywords(); 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)); window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result));
} }
......
...@@ -3,6 +3,10 @@ import type React from 'react'; ...@@ -3,6 +3,10 @@ import type React from 'react';
import type { Configuration } from 'rollbar'; import type { Configuration } from 'rollbar';
import config from 'configs/app'; 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; const feature = config.features.rollbar;
...@@ -20,4 +24,42 @@ export const clientConfig: Configuration | undefined = feature.isEnabled ? { ...@@ -20,4 +24,42 @@ export const clientConfig: Configuration | undefined = feature.isEnabled ? {
code_version: feature.codeVersion, code_version: feature.codeVersion,
app_instance: feature.instance, 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; } : undefined;
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 };
}
import _upperFirst from 'lodash/upperFirst'; import { upperFirst } from 'es-toolkit';
import type { Metadata, MetadataAttributes } from 'types/client/token'; import type { Metadata, MetadataAttributes } from 'types/client/token';
...@@ -72,7 +72,7 @@ export default function attributesParser(attributes: Array<unknown>): Metadata[' ...@@ -72,7 +72,7 @@ export default function attributesParser(attributes: Array<unknown>): Metadata['
return { return {
...formatValue(value, display, trait), ...formatValue(value, display, trait),
trait_type: _upperFirst(trait || 'property'), trait_type: upperFirst(trait || 'property'),
}; };
}) })
.filter((item) => item?.value) .filter((item) => item?.value)
......
...@@ -12,7 +12,7 @@ const currentChain = { ...@@ -12,7 +12,7 @@ const currentChain = {
}, },
rpcUrls: { rpcUrls: {
'default': { 'default': {
http: [ config.chain.rpcUrl ?? '' ], http: config.chain.rpcUrls,
}, },
}, },
blockExplorers: { blockExplorers: {
......
import _get from 'lodash/get'; import { get } from 'es-toolkit/compat';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
...@@ -25,7 +25,7 @@ export default function useAddOrSwitchChain() { ...@@ -25,7 +25,7 @@ export default function useAddOrSwitchChain() {
const errorObj = getErrorObj(error); const errorObj = getErrorObj(error);
const code = errorObj && 'code' in errorObj ? errorObj.code : undefined; 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. // This error code indicates that the chain has not been added to Wallet.
if (code === 4902 || originalErrorCode === 4902) { if (code === 4902 || originalErrorCode === 4902) {
...@@ -37,7 +37,7 @@ export default function useAddOrSwitchChain() { ...@@ -37,7 +37,7 @@ export default function useAddOrSwitchChain() {
symbol: config.chain.currency.symbol, symbol: config.chain.currency.symbol,
decimals: config.chain.currency.decimals, decimals: config.chain.currency.decimals,
}, },
rpcUrls: [ config.chain.rpcUrl ], rpcUrls: config.chain.rpcUrls,
blockExplorerUrls: [ config.app.baseUrl ], blockExplorerUrls: [ config.app.baseUrl ],
} ] as never; } ] as never;
// in wagmi types for wallet_addEthereumChain method is not provided // in wagmi types for wallet_addEthereumChain method is not provided
......
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; import { WagmiAdapter } from '@reown/appkit-adapter-wagmi';
import { http } from 'viem'; import { fallback, http } from 'viem';
import { createConfig } from 'wagmi'; import { createConfig } from 'wagmi';
import config from 'configs/app'; import config from 'configs/app';
...@@ -13,7 +13,11 @@ const wagmi = (() => { ...@@ -13,7 +13,11 @@ const wagmi = (() => {
const wagmiConfig = createConfig({ const wagmiConfig = createConfig({
chains: [ currentChain ], chains: [ currentChain ],
transports: { 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, ssr: true,
batch: { multicall: { wait: 100 } }, batch: { multicall: { wait: 100 } },
...@@ -26,7 +30,7 @@ const wagmi = (() => { ...@@ -26,7 +30,7 @@ const wagmi = (() => {
networks: chains, networks: chains,
multiInjectedProviderDiscovery: true, multiInjectedProviderDiscovery: true,
transports: { transports: {
[currentChain.id]: http(), [currentChain.id]: fallback(config.chain.rpcUrls.map((url) => http(url))),
}, },
projectId: feature.walletConnect.projectId, projectId: feature.walletConnect.projectId,
ssr: true, ssr: true,
......
import _padStart from 'lodash/padStart'; import { padStart } from 'es-toolkit/compat';
import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block'; import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block';
...@@ -42,11 +42,11 @@ function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails { ...@@ -42,11 +42,11 @@ function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails {
amount: `${ 100 - index }210001063118670575`, amount: `${ 100 - index }210001063118670575`,
account: { account: {
...addressMock.withoutName, ...addressMock.withoutName,
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ _padStart(String(index), 2, '0') }`, hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`,
}, },
associated_account: { associated_account: {
...addressMock.withoutName, ...addressMock.withoutName,
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ _padStart(String(index), 2, '0') }`, hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`,
}, },
}; };
} }
......
import _mapValues from 'lodash/mapValues'; import { mapValues } from 'es-toolkit';
import type { HomeStats } from 'types/api/stats'; import type { HomeStats } from 'types/api/stats';
...@@ -51,17 +51,17 @@ export const withBtcLocked: HomeStats = { ...@@ -51,17 +51,17 @@ export const withBtcLocked: HomeStats = {
export const withoutFiatPrices: HomeStats = { export const withoutFiatPrices: HomeStats = {
...base, ...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 = { export const withoutGweiPrices: HomeStats = {
...base, ...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 = { export const withoutBothPrices: HomeStats = {
...base, ...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 = { export const withoutGasInfo: HomeStats = {
......
...@@ -51,7 +51,7 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -51,7 +51,7 @@ export function app(): CspDev.DirectiveDescriptor {
getFeaturePayload(config.features.rewards)?.api.endpoint, getFeaturePayload(config.features.rewards)?.api.endpoint,
// chain RPC server // chain RPC server
config.chain.rpcUrl, ...config.chain.rpcUrls,
'https://infragrid.v.network', // RPC providers 'https://infragrid.v.network', // RPC providers
// github (spec for api-docs page) // github (spec for api-docs page)
......
import type CspDev from 'csp-dev'; import type CspDev from 'csp-dev';
import { uniq } from 'es-toolkit';
export const KEY_WORDS = { export const KEY_WORDS = {
BLOB: 'blob:', BLOB: 'blob:',
...@@ -11,17 +12,6 @@ export const KEY_WORDS = { ...@@ -11,17 +12,6 @@ export const KEY_WORDS = {
UNSAFE_EVAL: '\'unsafe-eval\'', 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>) { export function mergeDescriptors(...descriptors: Array<CspDev.DirectiveDescriptor>) {
return descriptors.reduce((result, item) => { return descriptors.reduce((result, item) => {
for (const _key in item) { for (const _key in item) {
...@@ -50,7 +40,7 @@ export function makePolicyString(policyDescriptor: CspDev.DirectiveDescriptor) { ...@@ -50,7 +40,7 @@ export function makePolicyString(policyDescriptor: CspDev.DirectiveDescriptor) {
return; return;
} }
const uniqueValues = unique(value); const uniqueValues = uniq(value);
return [ key, uniqueValues.join(' ') ].join(' '); return [ key, uniqueValues.join(' ') ].join(' ');
}) })
.filter(Boolean) .filter(Boolean)
......
import { pick } from 'es-toolkit';
import type { IncomingMessage } from 'http'; import type { IncomingMessage } from 'http';
import _pick from 'lodash/pick';
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import type { NextApiRequestCookies } from 'next/dist/server/api-utils'; import type { NextApiRequestCookies } from 'next/dist/server/api-utils';
import type { RequestInit, Response } from 'node-fetch'; import type { RequestInit, Response } from 'node-fetch';
...@@ -21,7 +21,7 @@ export default function fetchFactory( ...@@ -21,7 +21,7 @@ export default function fetchFactory(
accept: _req.headers['accept'] || 'application/json', accept: _req.headers['accept'] || 'application/json',
'content-type': _req.headers['content-type'] || 'application/json', 'content-type': _req.headers['content-type'] || 'application/json',
cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '', cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '',
..._pick(_req.headers, [ ...pick(_req.headers, [
'x-csrf-token', 'x-csrf-token',
'Authorization', // the old value, just in case 'Authorization', // the old value, just in case
'authorization', // Node.js automatically lowercases headers 'authorization', // Node.js automatically lowercases headers
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
"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",
"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: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" "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 @@ ...@@ -80,6 +82,7 @@
"dappscout-iframe": "0.2.5", "dappscout-iframe": "0.2.5",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"es-toolkit": "1.31.0",
"focus-visible": "^5.2.0", "focus-visible": "^5.2.0",
"getit-sdk": "^1.0.4", "getit-sdk": "^1.0.4",
"gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git", "gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
...@@ -87,7 +90,6 @@ ...@@ -87,7 +90,6 @@
"graphql": "^16.8.1", "graphql": "^16.8.1",
"graphql-ws": "^5.11.3", "graphql-ws": "^5.11.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.0.0",
"magic-bytes.js": "1.8.0", "magic-bytes.js": "1.8.0",
"mixpanel-browser": "^2.47.0", "mixpanel-browser": "^2.47.0",
"monaco-editor": "^0.34.1", "monaco-editor": "^0.34.1",
......
import _pick from 'lodash/pick'; import { pick, pickBy } from 'es-toolkit';
import _pickBy from 'lodash/pickBy';
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'nextjs/utils/fetchProxy'; import fetchFactory from 'nextjs/utils/fetchProxy';
...@@ -18,7 +17,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => { ...@@ -18,7 +17,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => {
); );
const apiRes = await fetchFactory(nextReq)( const apiRes = await fetchFactory(nextReq)(
url.toString(), url.toString(),
_pickBy(_pick(nextReq, [ 'body', 'method' ]), Boolean), pickBy(pick(nextReq, [ 'body', 'method' ]), Boolean),
); );
// proxy some headers from API // proxy some headers from API
......
import type { TestFixture, Page } from '@playwright/test'; 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 { encodeFunctionData, encodeFunctionResult, type AbiFunction } from 'viem';
import { getEnvValue } from 'configs/app/utils'; import { getEnvValue } from 'configs/app/utils';
...@@ -43,7 +43,7 @@ const fixture: TestFixture<MockContractReadResponseFixture, { page: Page }> = as ...@@ -43,7 +43,7 @@ const fixture: TestFixture<MockContractReadResponseFixture, { page: Page }> = as
value: params?.value, value: params?.value,
}; };
if (_isEqual(params, callParams) && id) { if (isEqual(params, callParams) && id) {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
json: { json: {
......
import type { TestFixture, Page } from '@playwright/test'; import type { TestFixture, Page } from '@playwright/test';
import _isEqual from 'lodash/isEqual'; import { isEqual } from 'es-toolkit';
import type { PublicRpcSchema } from 'viem'; import type { PublicRpcSchema } from 'viem';
import { getEnvValue } from 'configs/app/utils'; import { getEnvValue } from 'configs/app/utils';
...@@ -34,7 +34,7 @@ const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ pag ...@@ -34,7 +34,7 @@ const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ pag
...(rpcMock.Parameters ? { params: rpcMock.Parameters } : {}), ...(rpcMock.Parameters ? { params: rpcMock.Parameters } : {}),
}; };
if (_isEqual(json, payload) && id !== undefined) { if (isEqual(json, payload) && id !== undefined) {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
json: { json: {
......
import './fonts.css'; import './fonts.css';
import './index.css'; import './index.css';
import { beforeMount } from '@playwright/experimental-ct-react/hooks'; import { beforeMount } from '@playwright/experimental-ct-react/hooks';
import _defaultsDeep from 'lodash/defaultsDeep';
import MockDate from 'mockdate'; import MockDate from 'mockdate';
import * as router from 'next/router'; import * as router from 'next/router';
...@@ -12,12 +11,15 @@ const NEXT_ROUTER_MOCK = { ...@@ -12,12 +11,15 @@ const NEXT_ROUTER_MOCK = {
replace: () => Promise.resolve(), replace: () => Promise.resolve(),
}; };
beforeMount(async({ hooksConfig }) => { beforeMount(async({ hooksConfig }: { hooksConfig?: { router: typeof router } }) => {
// Before mount, redefine useRouter to return mock value from test. // Before mount, redefine useRouter to return mock value from test.
// @ts-ignore: I really want to redefine this property :) // @ts-ignore: I really want to redefine this property :)
// eslint-disable-next-line no-import-assign // 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 // set current date
MockDate.set('2022-11-11T12:00:00Z'); MockDate.set('2022-11-11T12:00:00Z');
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
// eslint-disable-next-line no-restricted-imports
import { Skeleton as SkeletonComponent } from '@chakra-ui/react'; import { Skeleton as SkeletonComponent } from '@chakra-ui/react';
import { import {
defineStyle, defineStyle,
......
...@@ -41,6 +41,7 @@ const LOCAL_ENVS = { ...@@ -41,6 +41,7 @@ const LOCAL_ENVS = {
const IGNORED_ENVS = [ const IGNORED_ENVS = [
'NEXT_PUBLIC_GIT_COMMIT_SHA', 'NEXT_PUBLIC_GIT_COMMIT_SHA',
'NEXT_PUBLIC_GIT_TAG', 'NEXT_PUBLIC_GIT_TAG',
'NEXT_PUBLIC_ICON_SPRITE_HASH',
]; ];
function parseScriptArgs() { function parseScriptArgs() {
......
#!/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'
\ No newline at end of file
#!/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'
\ No newline at end of file
import type * as bens from '@blockscout/bens-types';
import type { TokenType } from 'types/api/token'; import type { TokenType } from 'types/api/token';
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract'; export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
...@@ -47,6 +48,7 @@ export interface SearchResultDomain { ...@@ -47,6 +48,7 @@ export interface SearchResultDomain {
expiry_date?: string; expiry_date?: string;
name: string; name: string;
names_count: number; names_count: number;
protocol?: bens.ProtocolInfo;
}; };
} }
......
import { chakra, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react'; import { chakra, Tooltip, Hide, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { CsvExportParams } from 'types/client/address'; import type { CsvExportParams } from 'types/client/address';
...@@ -8,6 +8,7 @@ import { route } from 'nextjs-routes'; ...@@ -8,6 +8,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
......
import { Box, Flex, Skeleton, Text } from '@chakra-ui/react'; import { Box, Flex, Text } from '@chakra-ui/react';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { NovesResponseData } from 'types/api/noves'; import type { NovesResponseData } from 'types/api/noves';
import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
......
import { Td, Tr, Skeleton, Box } from '@chakra-ui/react'; import { Td, Tr, Box } from '@chakra-ui/react';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { NovesResponseData } from 'types/api/noves'; import type { NovesResponseData } from 'types/api/noves';
import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; import NovesFromTo from 'ui/shared/Noves/NovesFromTo';
......
import { Text, Flex, Skeleton } from '@chakra-ui/react'; import { Text, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import config from 'configs/app'; ...@@ -8,6 +8,7 @@ import config from 'configs/app';
import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
import Skeleton from 'ui/shared/chakra/Skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......
import { Td, Tr, Flex, Skeleton } from '@chakra-ui/react'; import { Td, Tr, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import type { Block } from 'types/api/block'; ...@@ -7,6 +7,7 @@ import type { Block } from 'types/api/block';
import config from 'configs/app'; import config from 'configs/app';
import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
import Skeleton from 'ui/shared/chakra/Skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......
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 BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,7 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; ...@@ -6,6 +6,7 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { WEI, ZERO } from 'lib/consts'; import { WEI, ZERO } from 'lib/consts';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import Skeleton from 'ui/shared/chakra/Skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
......
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 BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { WEI, ZERO } from 'lib/consts'; import { WEI, ZERO } from 'lib/consts';
import Skeleton from 'ui/shared/chakra/Skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......
...@@ -6,7 +6,6 @@ import { ...@@ -6,7 +6,6 @@ import {
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
Image, Image,
Skeleton,
useDisclosure, useDisclosure,
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
...@@ -14,6 +13,7 @@ import React from 'react'; ...@@ -14,6 +13,7 @@ import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
......
import { Button, Skeleton } from '@chakra-ui/react'; import { Button } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton';
interface Props { interface Props {
isLoading: boolean; isLoading: boolean;
addressHash: string; addressHash: string;
......
...@@ -10,7 +10,6 @@ import { ...@@ -10,7 +10,6 @@ import {
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
Skeleton,
StackDivider, StackDivider,
useDisclosure, useDisclosure,
VStack, VStack,
...@@ -22,6 +21,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract'; ...@@ -22,6 +21,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
import { chakra, Flex, Skeleton } from '@chakra-ui/react'; import { chakra, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkNewTab from 'ui/shared/links/LinkNewTab'; import LinkNewTab from 'ui/shared/links/LinkNewTab';
......
import { Flex, Skeleton, Text, Tooltip } from '@chakra-ui/react'; import { Flex, Text, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SmartContract } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
...@@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract'; ...@@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import formatLanguageName from 'lib/contracts/formatLanguageName'; import formatLanguageName from 'lib/contracts/formatLanguageName';
import Skeleton from 'ui/shared/chakra/Skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import CodeEditor from 'ui/shared/monaco/CodeEditor'; import CodeEditor from 'ui/shared/monaco/CodeEditor';
......
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 type { Channel } from 'phoenix';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract'; ...@@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
......
import noop from 'lodash/noop'; import { noop } from 'es-toolkit';
import React from 'react'; import React from 'react';
import { test, expect } from 'playwright/lib'; import { test, expect } from 'playwright/lib';
......
import { chakra, useColorModeValue, Flex, GridItem, Skeleton } from '@chakra-ui/react'; import { chakra, useColorModeValue, Flex, GridItem } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Hint from 'ui/shared/Hint'; import Hint from 'ui/shared/Hint';
interface Props { interface Props {
......
import { Accordion, Box, Flex, Link } from '@chakra-ui/react'; import { Accordion, Box, Flex, Link } from '@chakra-ui/react';
import _range from 'lodash/range'; import { range } from 'es-toolkit';
import React from 'react'; import React from 'react';
import type { SmartContractMethod } from './types'; import type { SmartContractMethod } from './types';
...@@ -39,7 +39,7 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro ...@@ -39,7 +39,7 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro
} }
if (expandedSections.length < abi.length) { if (expandedSections.length < abi.length) {
setExpandedSections(_range(0, abi.length)); setExpandedSections(range(0, abi.length));
} else { } else {
setExpandedSections([]); setExpandedSections([]);
} }
......
import { Alert, Button, Flex, Skeleton } from '@chakra-ui/react'; import { Alert, Button, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useWeb3Wallet from 'lib/web3/useWallet'; import useWeb3Wallet from 'lib/web3/useWallet';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props { interface Props {
......
import { Alert, Skeleton } from '@chakra-ui/react'; import { Alert } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton';
interface Props { interface Props {
isLoading?: boolean; isLoading?: boolean;
} }
......
import { Button, Flex, Skeleton, useDisclosure } from '@chakra-ui/react'; import { Button, Flex, useDisclosure } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract'; ...@@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal'; import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
import Skeleton from 'ui/shared/chakra/Skeleton';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
import AuthGuard from 'ui/snippets/auth/AuthGuard'; import AuthGuard from 'ui/snippets/auth/AuthGuard';
import useIsAuth from 'ui/snippets/auth/useIsAuth'; import useIsAuth from 'ui/snippets/auth/useIsAuth';
......
...@@ -28,7 +28,9 @@ interface Props { ...@@ -28,7 +28,9 @@ interface Props {
} }
const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDisabled, isOptional: isOptionalProp, level }: 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 isNativeCoin = data.fieldType === 'native_coin';
const isOptional = isOptionalProp || isNativeCoin; const isOptional = isOptionalProp || isNativeCoin;
...@@ -46,6 +48,8 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -46,6 +48,8 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64; const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64;
React.useImperativeHandle(field.ref, () => ref.current);
const handleChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const formattedValue = format(event.target.value); const formattedValue = format(event.target.value);
field.onChange(formattedValue); // data send back to hook form field.onChange(formattedValue); // data send back to hook form
...@@ -83,6 +87,42 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -83,6 +87,42 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
setValue(name, newValue, { shouldValidate: true }); setValue(name, newValue, { shouldValidate: true });
}, [ format, name, setValue ]); }, [ 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; const error = fieldState.error;
return ( return (
...@@ -107,9 +147,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -107,9 +147,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
thousandSeparator: ' ', thousandSeparator: ' ',
decimalScale: 0, decimalScale: 0,
allowNegative: !argTypeMatchInt.isUnsigned, 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 } onChange={ handleChange }
onPaste={ handlePaste }
required={ !isOptional } required={ !isOptional }
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
placeholder={ data.type } placeholder={ data.type }
...@@ -148,7 +193,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -148,7 +193,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
Max Max
</Button> </Button>
)) } )) }
{ hasMultiplyButton && <ContractMethodMultiplyButton onClick={ handleMultiplyButtonClick } isDisabled={ isDisabled }/> } { hasMultiplyButton && (
<ContractMethodMultiplyButton
onClick={ handleMultiplyButtonClick }
isDisabled={ isDisabled }
initialValue={ intPower }
onChange={ setIntPower }
/>
) }
</InputRightElement> </InputRightElement>
</InputGroup> </InputGroup>
{ error && <Box color="error" fontSize="sm" lineHeight={ 5 } mt={ 1 }>{ error.message }</Box> } { error && <Box color="error" fontSize="sm" lineHeight={ 5 } mt={ 1 }>{ error.message }</Box> }
......
...@@ -20,10 +20,12 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -20,10 +20,12 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
onClick: (power: number) => void; onClick: (power: number) => void;
isDisabled?: boolean; isDisabled?: boolean;
initialValue: number;
onChange: (power: number) => void;
} }
const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => { const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onChange }: Props) => {
const [ selectedOption, setSelectedOption ] = React.useState<number | undefined>(18); const [ selectedOption, setSelectedOption ] = React.useState<number | undefined>(initialValue);
const [ customValue, setCustomValue ] = React.useState<number>(); const [ customValue, setCustomValue ] = React.useState<number>();
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
...@@ -35,13 +37,16 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => { ...@@ -35,13 +37,16 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => {
setSelectedOption((prev) => prev === id ? undefined : id); setSelectedOption((prev) => prev === id ? undefined : id);
setCustomValue(undefined); setCustomValue(undefined);
onClose(); onClose();
onChange(id);
} }
}, [ onClose ]); }, [ onClose, onChange ]);
const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setCustomValue(Number(event.target.value)); const value = Number(event.target.value);
setCustomValue(value);
setSelectedOption(undefined); setSelectedOption(undefined);
}, []); onChange(value);
}, [ onChange ]);
const value = selectedOption || customValue; const value = selectedOption || customValue;
......
import _set from 'lodash/set'; import { set } from 'es-toolkit/compat';
import type { ContractAbiItemInput } from '../types'; import type { ContractAbiItemInput } from '../types';
...@@ -78,7 +78,7 @@ export function transformFormDataToMethodArgs(formData: ContractMethodFormFields ...@@ -78,7 +78,7 @@ export function transformFormDataToMethodArgs(formData: ContractMethodFormFields
for (const field in formData) { for (const field in formData) {
const value = formData[field]; const value = formData[field];
_set(result, field.replaceAll(':', '.'), value); set(result, field.replaceAll(':', '.'), value);
} }
const filteredResult = filterOutEmptyItems(result); const filteredResult = filterOutEmptyItems(result);
......
import _pickBy from 'lodash/pickBy'; import { pickBy } from 'es-toolkit';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -63,7 +63,7 @@ export default function useMethodsFilters({ abi }: Params) { ...@@ -63,7 +63,7 @@ export default function useMethodsFilters({ abi }: Params) {
return; 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( router.push(
{ pathname: router.pathname, query: { ...queryForPathname, tab: nextTab } }, { pathname: router.pathname, query: { ...queryForPathname, tab: nextTab } },
undefined, undefined,
......
import { Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -8,6 +7,7 @@ import type { AddressCounters } from 'types/api/address'; ...@@ -8,6 +7,7 @@ import type { AddressCounters } from 'types/api/address';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import Skeleton from 'ui/shared/chakra/Skeleton';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
interface Props { interface Props {
......
import { Image, Tooltip } from '@chakra-ui/react'; import { Image, Tooltip } from '@chakra-ui/react';
import _capitalize from 'lodash/capitalize'; import { capitalize } from 'es-toolkit';
import React from 'react'; import React from 'react';
import type { MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig'; import type { MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig';
...@@ -25,10 +25,10 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider ...@@ -25,10 +25,10 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider
const buttonContent = hasSingleProvider ? ( const buttonContent = hasSingleProvider ? (
<> <>
{ buttonIcon } { buttonIcon }
{ _capitalize(item.name) } { capitalize(item.name) }
</> </>
) : ( ) : (
<Tooltip label={ _capitalize(item.name) }>{ buttonIcon }</Tooltip> <Tooltip label={ capitalize(item.name) }>{ buttonIcon }</Tooltip>
); );
const linkProps = { const linkProps = {
......
import { Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import Skeleton from 'ui/shared/chakra/Skeleton';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
......
import { Skeleton, Text, Flex } from '@chakra-ui/react'; import { Text, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
...@@ -6,6 +6,7 @@ import type { Address } from 'types/api/address'; ...@@ -6,6 +6,7 @@ import type { Address } from 'types/api/address';
import config from 'configs/app'; import config from 'configs/app';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Skeleton from 'ui/shared/chakra/Skeleton';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
import { getTokensTotalInfo } from '../utils/tokenUtils'; import { getTokensTotalInfo } from '../utils/tokenUtils';
......
...@@ -12,7 +12,6 @@ import { ...@@ -12,7 +12,6 @@ import {
useDisclosure, useDisclosure,
Tooltip, Tooltip,
IconButton, IconButton,
Skeleton,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
...@@ -23,6 +22,7 @@ import type { Address as AddressType } from 'types/api/address'; ...@@ -23,6 +22,7 @@ import type { Address as AddressType } from 'types/api/address';
import getPageType from 'lib/mixpanel/getPageType'; import getPageType from 'lib/mixpanel/getPageType';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { useRollbar } from 'lib/rollbar'; import { useRollbar } from 'lib/rollbar';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
import { Image, Skeleton } from '@chakra-ui/react'; import { Image } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import * as v from 'valibot'; import * as v from 'valibot';
import config from 'configs/app'; import config from 'configs/app';
import Skeleton from 'ui/shared/chakra/Skeleton';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
......
...@@ -8,12 +8,11 @@ import { ...@@ -8,12 +8,11 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
Show, Show,
Skeleton,
useDisclosure, useDisclosure,
chakra, chakra,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import _clamp from 'lodash/clamp'; import { clamp } from 'es-toolkit';
import React from 'react'; import React from 'react';
import type * as bens from '@blockscout/bens-types'; import type * as bens from '@blockscout/bens-types';
...@@ -23,6 +22,7 @@ import { route } from 'nextjs-routes'; ...@@ -23,6 +22,7 @@ import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import Skeleton from 'ui/shared/chakra/Skeleton';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
...@@ -37,7 +37,7 @@ interface Props { ...@@ -37,7 +37,7 @@ interface Props {
const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => { const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => {
return ( return (
<Grid <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 } columnGap={ 8 }
rowGap={ 4 } rowGap={ 4 }
mt={ 2 } mt={ 2 }
......
import { Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressEpochRewardsItem } from 'types/api/address'; import type { AddressEpochRewardsItem } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
......
import { Flex, Td, Tr, Text, Skeleton } from '@chakra-ui/react'; import { Flex, Td, Tr, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressEpochRewardsItem } from 'types/api/address'; import type { AddressEpochRewardsItem } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
......
import { Flex, HStack, Skeleton } from '@chakra-ui/react'; import { Flex, HStack } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; ...@@ -7,6 +7,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import AddressFromTo from 'ui/shared/address/AddressFromTo'; import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
......
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 BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; ...@@ -6,6 +6,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import AddressFromTo from 'ui/shared/address/AddressFromTo'; import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
......
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 { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address'; ...@@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
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 { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address'; ...@@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
......
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 { useQueryClient, useIsFetching } from '@tanstack/react-query';
import _sumBy from 'lodash/sumBy'; import { sumBy } from 'es-toolkit';
import NextLink from 'next/link'; import NextLink from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -11,6 +11,7 @@ import { getResourceKey } from 'lib/api/useApiQuery'; ...@@ -11,6 +11,7 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import useFetchTokens from '../utils/useFetchTokens'; import useFetchTokens from '../utils/useFetchTokens';
...@@ -49,7 +50,7 @@ const TokenSelect = ({ onClick }: Props) => { ...@@ -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) { if (isError || !hasTokens) {
return <Box py="6px">0</Box>; return <Box py="6px">0</Box>;
} }
......
import { Box, Button, Skeleton, chakra, useColorModeValue } from '@chakra-ui/react'; import { Box, Button, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FormattedData } from './types'; import type { FormattedData } from './types';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import { getTokensTotalInfo } from '../utils/tokenUtils'; import { getTokensTotalInfo } from '../utils/tokenUtils';
......
import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react'; 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 type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
...@@ -26,7 +26,7 @@ interface Props { ...@@ -26,7 +26,7 @@ interface Props {
const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => { const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => {
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); 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 ( return (
<> <>
......
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.
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