diff --git a/.env.example b/.env.example index 1b06de63c5..a1f46cb03f 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,8 @@ NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx -NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=xxx \ No newline at end of file +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 diff --git a/.gitignore b/.gitignore index 8c1b8b71af..d4459df0b2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ /public/icons/sprite.svg /public/icons/sprite.*.svg /public/icons/README.md +/public/static/og_image.png +/public/sitemap.xml +/public/robots.txt /analyze # production diff --git a/Dockerfile b/Dockerfile index 31a2e5ef5e..36eadc261a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,12 @@ WORKDIR /favicon-generator COPY ./deploy/tools/favicon-generator/package.json ./deploy/tools/favicon-generator/yarn.lock ./ RUN yarn --frozen-lockfile --network-timeout 100000 +### SITEMAP GENERATOR +# Install dependencies +WORKDIR /sitemap-generator +COPY ./deploy/tools/sitemap-generator/package.json ./deploy/tools/sitemap-generator/yarn.lock ./ +RUN yarn --frozen-lockfile --network-timeout 100000 + # ***************************** # ****** STAGE 2: Build ******* @@ -88,6 +94,12 @@ RUN cd ./deploy/tools/envs-validator && yarn build # Copy dependencies and source code COPY --from=deps /favicon-generator/node_modules ./deploy/tools/favicon-generator/node_modules + +### SITEMAP GENERATOR +# Copy dependencies and source code +COPY --from=deps /sitemap-generator/node_modules ./deploy/tools/sitemap-generator/node_modules + + # ***************************** # ******* STAGE 3: Run ******** # ***************************** @@ -122,11 +134,16 @@ COPY --chmod=755 ./deploy/scripts/validate_envs.sh . COPY --chmod=755 ./deploy/scripts/make_envs_script.sh . ## Assets downloader COPY --chmod=755 ./deploy/scripts/download_assets.sh . +## OG image generator +COPY ./deploy/scripts/og_image_generator.js . ## Favicon generator COPY --chmod=755 ./deploy/scripts/favicon_generator.sh . COPY --from=builder /app/deploy/tools/favicon-generator ./deploy/tools/favicon-generator RUN ["chmod", "-R", "777", "./deploy/tools/favicon-generator"] RUN ["chmod", "-R", "777", "./public"] +## Sitemap generator +COPY --chmod=755 ./deploy/scripts/sitemap_generator.sh . +COPY --from=builder /app/deploy/tools/sitemap-generator ./deploy/tools/sitemap-generator # Copy ENVs files COPY --from=builder /app/.env.registry . diff --git a/configs/app/chain.ts b/configs/app/chain.ts index 56971abda6..e50647530f 100644 --- a/configs/app/chain.ts +++ b/configs/app/chain.ts @@ -1,7 +1,9 @@ import type { RollupType } from 'types/client/rollup'; import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks'; -import { getEnvValue } from './utils'; +import { urlValidator } from 'ui/shared/forms/validators/url'; + +import { getEnvValue, parseEnvJson } from './utils'; const DEFAULT_CURRENCY_DECIMALS = 18; @@ -17,6 +19,19 @@ const verificationType: NetworkVerificationType = (() => { return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining'; })(); +const rpcUrls = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'); + const isUrl = urlValidator(envValue); + + if (envValue && isUrl === true) { + return [ envValue ]; + } + + const parsedValue = parseEnvJson>(envValue); + + return Array.isArray(parsedValue) ? parsedValue : []; +})(); + const chain = Object.freeze({ id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'), name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'), @@ -32,7 +47,7 @@ const chain = Object.freeze({ }, hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true', tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', - rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), + rpcUrls, isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', verificationType, }); diff --git a/configs/app/features/blockchainInteraction.ts b/configs/app/features/blockchainInteraction.ts index 788e059ec9..6700126089 100644 --- a/configs/app/features/blockchainInteraction.ts +++ b/configs/app/features/blockchainInteraction.ts @@ -17,7 +17,7 @@ const config: Feature<{ walletConnect: { projectId: string } }> = (() => { chain.currency.name && chain.currency.symbol && chain.currency.decimals && - chain.rpcUrl && + chain.rpcUrls.length > 0 && walletConnectProjectId ) { return Object.freeze({ diff --git a/configs/app/features/marketplace.ts b/configs/app/features/marketplace.ts index ab5ab4a965..f0af944b10 100644 --- a/configs/app/features/marketplace.ts +++ b/configs/app/features/marketplace.ts @@ -33,7 +33,7 @@ const config: Feature<( rating: { airtableApiKey: string; airtableBaseId: string } | undefined; graphLinksUrl: string | undefined; }> = (() => { - if (enabled === 'true' && chain.rpcUrl && submitFormUrl) { + if (enabled === 'true' && chain.rpcUrls.length > 0 && submitFormUrl) { const props = { submitFormUrl, categoriesUrl, diff --git a/configs/app/features/rollup.ts b/configs/app/features/rollup.ts index 3728e60c18..e8f5cac92f 100644 --- a/configs/app/features/rollup.ts +++ b/configs/app/features/rollup.ts @@ -31,7 +31,7 @@ const config: Feature<{ type, L1BaseUrl: stripTrailingSlash(L1BaseUrl), L2WithdrawalUrl: type === 'optimistic' ? L2WithdrawalUrl : undefined, - outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') !== 'false', + outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') === 'true', parentChainName: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME') : undefined, homepage: { showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', diff --git a/configs/app/meta.ts b/configs/app/meta.ts index 3d7b777e03..87f6405282 100644 --- a/configs/app/meta.ts +++ b/configs/app/meta.ts @@ -1,7 +1,7 @@ import app from './app'; import { getEnvValue, getExternalAssetFilePath } from './utils'; -const defaultImageUrl = '/static/og_placeholder.png'; +const defaultImageUrl = '/static/og_image.png'; const meta = Object.freeze({ promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'false' ? false : true, diff --git a/configs/envs/.env.shibarium b/configs/envs/.env.shibarium index dde91dbeb8..9497d3085e 100644 --- a/configs/envs/.env.shibarium +++ b/configs/envs/.env.shibarium @@ -1,6 +1,6 @@ # Set of ENVs for Shibarium network explorer # https://www.shibariumscan.io -# This is an auto-generated file. To update all values, run "yarn preset:sync --name=shibarium" +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=shibarium" # Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http @@ -23,6 +23,7 @@ NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethere NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/shibarium-mainnet.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xce531d29c0c469fb00b443b8091b8c059b4f13d7e025dd0ef843401d02b9a1a9 NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true @@ -31,15 +32,17 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(180deg, rgba(224, 111, 44, 1) 0%, rgba(228, 144, 52, 1) 100%)'],'text_color':['rgba(255, 255, 255, 1)']} NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_LOGOUT_URL=https://shibarium.us.auth0.com/v2/logout -NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Participated in our recent Blockscout activities? Check your eligibility and claim your NFT Scout badges. More exciting things are coming soon!

+NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Joined recent campaigns? Mint your Merit Badge here

+NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=shibarium NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_ENABLED=true -NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=patbqG4V2CI998jAq.9810c58c9de973ba2650621c94559088cbdfa1a914498e385621ed035d33c0d0 NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=BONE diff --git a/configs/envs/.env.zkevm b/configs/envs/.env.zkevm index 65db320a6b..f7e9b610dd 100644 --- a/configs/envs/.env.zkevm +++ b/configs/envs/.env.zkevm @@ -13,23 +13,31 @@ NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=zkevm.blockscout.com NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zkevm.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x25fcb396fc8652dcd0040f677a1dcc6fecff390ecafc815894379a3f254f1aa9 +NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%) -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) -NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%)'],'text_color':['rgba(255, 255, 255, 1)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-polygon.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/polygon-zkevm/pools'}}] NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg NEXT_PUBLIC_NETWORK_ID=1101 diff --git a/deploy/scripts/entrypoint.sh b/deploy/scripts/entrypoint.sh index ac1f91d6c5..d4a7cd8134 100755 --- a/deploy/scripts/entrypoint.sh +++ b/deploy/scripts/entrypoint.sh @@ -35,6 +35,9 @@ export_envs_from_preset() { # If there is a preset, load the environment variables from the its file export_envs_from_preset +# Generate OG image +node --no-warnings ./og_image_generator.js + # Download external assets ./download_assets.sh ./public/assets/configs @@ -61,6 +64,9 @@ echo # Create envs.js file with run-time environment variables for the client app ./make_envs_script.sh +# Generate sitemap.xml and robots.txt files +./sitemap_generator.sh + # Print list of enabled features node ./feature-reporter.js diff --git a/deploy/scripts/og_image_generator.js b/deploy/scripts/og_image_generator.js new file mode 100755 index 0000000000..d22f63b12b --- /dev/null +++ b/deploy/scripts/og_image_generator.js @@ -0,0 +1,61 @@ +/* eslint-disable no-console */ +import fs from 'fs'; +import path from 'path'; + +console.log('🎨 Generating OG image...'); + +const targetFile = path.resolve(process.cwd(), 'public/static/og_image.png'); + +function copyPlaceholderImage() { + const sourceFile = path.resolve(process.cwd(), 'public/static/og_placeholder.png'); + fs.copyFileSync(sourceFile, targetFile); +} + +if (process.env.NEXT_PUBLIC_OG_IMAGE_URL) { + console.log('⏩ NEXT_PUBLIC_OG_IMAGE_URL is set. Skipping OG image generation...'); +} else if (!process.env.NEXT_PUBLIC_NETWORK_NAME) { + console.log('⏩ NEXT_PUBLIC_NETWORK_NAME is not set. Copying placeholder image...'); + copyPlaceholderImage(); +} else if (!process.env.NEXT_PUBLIC_NETWORK_LOGO && !process.env.NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG) { + console.log('⏩ Neither NEXT_PUBLIC_NETWORK_LOGO nor NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG is set. Copying placeholder image...'); + copyPlaceholderImage(); +} else { + try { + const bannerConfig = JSON.parse(process.env.NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG?.replaceAll('\'', '"') || '{}'); + const data = { + title: `${ process.env.NEXT_PUBLIC_NETWORK_NAME } explorer`, + logo_url: process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK ?? process.env.NEXT_PUBLIC_NETWORK_LOGO, + background: bannerConfig.background?.[0], + title_color: bannerConfig.text_color?.[0], + invert_logo: !process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK, + }; + + console.log('⏳ Making request to OG image generator service...'); + + const response = await fetch('https://bigs.services.blockscout.com/generate/og', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (response.ok) { + console.log('⬇️ Downloading the image...'); + const buffer = await response.arrayBuffer(); + const imageBuffer = Buffer.from(buffer); + fs.writeFileSync(targetFile, imageBuffer); + } else { + const payload = response.headers.get('Content-type')?.includes('application/json') ? await response.json() : await response.text(); + console.error('🛑 Failed to generate OG image. Response:', payload); + console.log('Copying placeholder image...'); + copyPlaceholderImage(); + } + } catch (error) { + console.error('🛑 Failed to generate OG image. Error:', error?.message); + console.log('Copying placeholder image...'); + copyPlaceholderImage(); + } +} + +console.log('✅ Done.'); diff --git a/deploy/scripts/sitemap_generator.sh b/deploy/scripts/sitemap_generator.sh new file mode 100644 index 0000000000..fbeb89b517 --- /dev/null +++ b/deploy/scripts/sitemap_generator.sh @@ -0,0 +1,2 @@ +cd ./deploy/tools/sitemap-generator +yarn next-sitemap \ No newline at end of file diff --git a/deploy/tools/affected-tests/yarn.lock b/deploy/tools/affected-tests/yarn.lock index bd755eda2b..ebfe2bd8be 100644 --- a/deploy/tools/affected-tests/yarn.lock +++ b/deploy/tools/affected-tests/yarn.lock @@ -484,9 +484,9 @@ ms@2.1.2: integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== node-source-walk@^6.0.0, node-source-walk@^6.0.1, node-source-walk@^6.0.2: version "6.0.2" diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 6ed8487d94..bed33149c7 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -587,7 +587,21 @@ const schema = yup NEXT_PUBLIC_NETWORK_NAME: yup.string().required(), NEXT_PUBLIC_NETWORK_SHORT_NAME: yup.string(), NEXT_PUBLIC_NETWORK_ID: yup.number().positive().integer().required(), - NEXT_PUBLIC_NETWORK_RPC_URL: yup.string().test(urlTest), + NEXT_PUBLIC_NETWORK_RPC_URL: yup + .mixed() + .test( + 'shape', + 'Invalid schema were provided for NEXT_PUBLIC_NETWORK_RPC_URL, it should be either array of URLs or URL string', + (data) => { + const isUrlSchema = yup.string().test(urlTest); + const isArrayOfUrlsSchema = yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string().test(urlTest)); + + return isUrlSchema.isValidSync(data) || isArrayOfUrlsSchema.isValidSync(data); + }), NEXT_PUBLIC_NETWORK_CURRENCY_NAME: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(), diff --git a/deploy/tools/envs-validator/test/.env.alt b/deploy/tools/envs-validator/test/.env.alt index 172a809a56..11e6bf78fb 100644 --- a/deploy/tools/envs-validator/test/.env.alt +++ b/deploy/tools/envs-validator/test/.env.alt @@ -5,4 +5,5 @@ NEXT_PUBLIC_HOMEPAGE_STATS=[] NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32'] NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx -NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated \ No newline at end of file +NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated +NEXT_PUBLIC_NETWORK_RPC_URL=['https://example.com','https://example2.com'] diff --git a/deploy/tools/envs-validator/yarn.lock b/deploy/tools/envs-validator/yarn.lock index 8259f31c56..9e94b604b1 100644 --- a/deploy/tools/envs-validator/yarn.lock +++ b/deploy/tools/envs-validator/yarn.lock @@ -340,9 +340,9 @@ commander@^2.20.0: integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" diff --git a/deploy/tools/favicon-generator/yarn.lock b/deploy/tools/favicon-generator/yarn.lock index af54e5366c..11198f8e1b 100644 --- a/deploy/tools/favicon-generator/yarn.lock +++ b/deploy/tools/favicon-generator/yarn.lock @@ -460,9 +460,9 @@ commander@^2.20.0: integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" diff --git a/deploy/tools/feature-reporter/yarn.lock b/deploy/tools/feature-reporter/yarn.lock index 783abddc02..9d40e1c4d8 100644 --- a/deploy/tools/feature-reporter/yarn.lock +++ b/deploy/tools/feature-reporter/yarn.lock @@ -357,9 +357,9 @@ commander@^9.0.0: integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" diff --git a/deploy/tools/sitemap-generator/.gitignore b/deploy/tools/sitemap-generator/.gitignore new file mode 100644 index 0000000000..30bc162798 --- /dev/null +++ b/deploy/tools/sitemap-generator/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/deploy/tools/sitemap-generator/next-sitemap.config.js b/deploy/tools/sitemap-generator/next-sitemap.config.js new file mode 100644 index 0000000000..40f84169f3 --- /dev/null +++ b/deploy/tools/sitemap-generator/next-sitemap.config.js @@ -0,0 +1,176 @@ +/* eslint-disable no-console */ +const path = require('path'); + +const stripTrailingSlash = (str) => str[str.length - 1] === '/' ? str.slice(0, -1) : str; + +const fetchResource = async(url, formatter) => { + console.log('🌀 [next-sitemap] Fetching resource:', url); + try { + const res = await fetch(url); + if (res.ok) { + const data = await res.json(); + console.log('✅ [next-sitemap] Data fetched for resource:', url); + return formatter(data); + } + } catch (error) { + console.log('🚨 [next-sitemap] Error fetching resource:', url, error); + } +}; + +const siteUrl = [ + process.env.NEXT_PUBLIC_APP_PROTOCOL || 'https', + '://', + process.env.NEXT_PUBLIC_APP_HOST, + process.env.NEXT_PUBLIC_APP_PORT && ':' + process.env.NEXT_PUBLIC_APP_PORT, +].filter(Boolean).join(''); + +const apiUrl = (() => { + const baseUrl = [ + process.env.NEXT_PUBLIC_API_PROTOCOL || 'https', + '://', + process.env.NEXT_PUBLIC_API_HOST, + process.env.NEXT_PUBLIC_API_PORT && ':' + process.env.NEXT_PUBLIC_API_PORT, + ].filter(Boolean).join(''); + + const basePath = stripTrailingSlash(process.env.NEXT_PUBLIC_API_BASE_PATH || ''); + + return `${ baseUrl }${ basePath }/api/v2`; +})(); + +/** @type {import('next-sitemap').IConfig} */ +module.exports = { + siteUrl, + generateIndexSitemap: false, + generateRobotsTxt: true, + sourceDir: path.resolve(process.cwd(), '../../../.next'), + outDir: path.resolve(process.cwd(), '../../../public'), + exclude: [ + '/account/*', + '/auth/*', + '/login', + '/sprite', + ], + transform: async(config, path) => { + switch (path) { + case '/mud-worlds': + if (process.env.NEXT_PUBLIC_HAS_MUD_FRAMEWORK !== 'true') { + return null; + } + break; + case '/batches': + case '/deposits': + if (!process.env.NEXT_PUBLIC_ROLLUP_TYPE) { + return null; + } + break; + case '/withdrawals': + if (!process.env.NEXT_PUBLIC_ROLLUP_TYPE && process.env.NEXT_PUBLIC_HAS_BEACON_CHAIN !== 'true') { + return null; + } + break; + case '/dispute-games': + if (process.env.NEXT_PUBLIC_ROLLUP_TYPE !== 'optimistic') { + return null; + } + break; + case '/blobs': + if (process.env.NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED !== 'true') { + return null; + } + break; + case '/name-domains': + if (!process.env.NEXT_PUBLIC_NAME_SERVICE_API_HOST) { + return null; + } + break; + case '/ops': + if (process.env.NEXT_PUBLIC_HAS_USER_OPS !== 'true') { + return null; + } + break; + case '/output-roots': + if (process.env.NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED !== 'true') { + return null; + } + break; + case '/pools': + if (process.env.NEXT_PUBLIC_DEX_POOLS_ENABLED !== 'true') { + return null; + } + break; + case '/advanced-filter': + if (process.env.NEXT_PUBLIC_ADVANCED_FILTER_ENABLED === 'false') { + return null; + } + break; + case '/apps': + if (process.env.NEXT_PUBLIC_MARKETPLACE_ENABLED !== 'true') { + return null; + } + break; + case '/api-docs': + if (process.env.NEXT_PUBLIC_API_SPEC_URL === 'none') { + return null; + } + break; + case '/gas-tracker': + if (process.env.NEXT_PUBLIC_GAS_TRACKER_ENABLED === 'false') { + return null; + } + break; + case '/graphql': + if (process.env.NEXT_PUBLIC_GRAPHIQL_TRANSACTION === 'none') { + return null; + } + break; + case '/stats': + if (!process.env.NEXT_PUBLIC_STATS_API_HOST) { + return null; + } + break; + case '/validators': + if (!process.env.NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE) { + return null; + } + break; + } + + return { + loc: path, + changefreq: undefined, + priority: undefined, + lastmod: config.autoLastmod ? new Date().toISOString() : undefined, + alternateRefs: config.alternateRefs ?? [], + }; + }, + additionalPaths: async(config) => { + const addresses = fetchResource( + `${ apiUrl }/addresses`, + (data) => data.items.map(({ hash }) => `/address/${ hash }`), + ); + const txs = fetchResource( + `${ apiUrl }/transactions?filter=validated`, + (data) => data.items.map(({ hash }) => `/tx/${ hash }`), + ); + const blocks = fetchResource( + `${ apiUrl }/blocks?type=block`, + (data) => data.items.map(({ height }) => `/block/${ height }`), + ); + const tokens = fetchResource( + `${ apiUrl }/tokens`, + (data) => data.items.map(({ address }) => `/token/${ address }`), + ); + const contracts = fetchResource( + `${ apiUrl }/smart-contracts`, + (data) => data.items.map(({ address }) => `/address/${ address.hash }?tab=contract`), + ); + + return Promise.all([ + ...(await addresses || []), + ...(await txs || []), + ...(await blocks || []), + ...(await tokens || []), + ...(await contracts || []), + ].map(path => config.transform(config, path))); + }, +}; diff --git a/deploy/tools/sitemap-generator/package.json b/deploy/tools/sitemap-generator/package.json new file mode 100644 index 0000000000..903f7a3261 --- /dev/null +++ b/deploy/tools/sitemap-generator/package.json @@ -0,0 +1,12 @@ +{ + "name": "sitemap-generator", + "version": "1.0.0", + "main": "index.js", + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "next-sitemap": "4.2.3" + } +} diff --git a/deploy/tools/sitemap-generator/yarn.lock b/deploy/tools/sitemap-generator/yarn.lock new file mode 100644 index 0000000000..165c1f0478 --- /dev/null +++ b/deploy/tools/sitemap-generator/yarn.lock @@ -0,0 +1,147 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@corex/deepmerge@^4.0.43": + version "4.0.43" + resolved "https://registry.yarnpkg.com/@corex/deepmerge/-/deepmerge-4.0.43.tgz#9bd42559ebb41cc5a7fb7cfeea5f231c20977dca" + integrity sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ== + +"@next/env@^13.4.3": + version "13.5.8" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.8.tgz#404d3b3e5881b6a0510500c6cc97e3589a2e6371" + integrity sha512-YmiG58BqyZ2FjrF2+5uZExL2BrLr8RTQzLXNDJ8pJr0O+rPlOeDPXp1p1/4OrR3avDidzZo3D8QO2cuDv1KCkw== + +"@nodelib/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" diff --git a/docs/ENVS.md b/docs/ENVS.md index c96fd7a319..a2272bbf2a 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -95,7 +95,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will | NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_SHORT_NAME | `string` | Used for SEO attributes (e.g, page description) | - | - | `OoG` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_ID | `number` | Chain id, see [https://chainlist.org](https://chainlist.org) for the reference | Required | - | `99` | v1.0.x+ | -| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference | - | - | `https://core.poa.network` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_RPC_URL | `string \| Array` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference. Can contain a single string value, or an array of urls. | - | - | `https://core.poa.network` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | Network currency name | - | - | `Ether` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of network currency subdenomination | - | `wei` | `duck` | v1.23.0+ | | NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ | @@ -455,7 +455,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ | | NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ | | NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ | -| NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `true` | `false` | v1.37.0+ | +| NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `false` | `true` | v1.37.0+ | | NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME | `string` | Set to customize L1 transaction status labels in the UI (e.g., "Sent to "). This setting is applicable only for Arbitrum-based chains. | - | - | `DuckChain` | v1.37.0+ |   diff --git a/eslint.config.mjs b/eslint.config.mjs index 840bf72b0b..c5c6cacca7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -27,7 +27,7 @@ const RESTRICTED_MODULES = { { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, { name: '@chakra-ui/react', - importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast' ], + importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast', 'Skeleton' ], message: 'Please use corresponding component or hook from ui/shared/chakra component instead', }, ], @@ -423,6 +423,7 @@ export default tseslint.config( 'pages/**', 'nextjs/**', 'playwright/**', + 'deploy/scripts/**', 'deploy/tools/**', 'middleware.ts', 'instrumentation*.ts', diff --git a/lib/address/getCheckedSummedAddress.ts b/lib/address/getCheckedSummedAddress.ts index 6cf744620d..5c92b9a390 100644 --- a/lib/address/getCheckedSummedAddress.ts +++ b/lib/address/getCheckedSummedAddress.ts @@ -1,8 +1,14 @@ import { getAddress } from 'viem'; +import config from 'configs/app'; + export default function getCheckedSummedAddress(address: string): string { try { - return getAddress(address); + return getAddress( + address, + // We need to pass chainId to getAddress to make it work correctly for some chains, e.g. Rootstock + config.chain.id ? Number(config.chain.id) : undefined, + ); } catch (error) { return address; } diff --git a/lib/errors/throwOnAbsentParamError.ts b/lib/errors/throwOnAbsentParamError.ts index 5978b600a5..db286c8911 100644 --- a/lib/errors/throwOnAbsentParamError.ts +++ b/lib/errors/throwOnAbsentParamError.ts @@ -1,5 +1,7 @@ +export const ABSENT_PARAM_ERROR_MESSAGE = 'Required param not provided'; + export default function throwOnAbsentParamError(param: unknown) { if (!param) { - throw new Error('Required param not provided', { cause: { status: 404 } }); + throw new Error(ABSENT_PARAM_ERROR_MESSAGE, { cause: { status: 404 } }); } } diff --git a/lib/hooks/useGetCsrfToken.tsx b/lib/hooks/useGetCsrfToken.tsx index e1db1e595f..51b540c383 100644 --- a/lib/hooks/useGetCsrfToken.tsx +++ b/lib/hooks/useGetCsrfToken.tsx @@ -5,11 +5,9 @@ import isNeedProxy from 'lib/api/isNeedProxy'; import { getResourceKey } from 'lib/api/useApiQuery'; import * as cookies from 'lib/cookies'; import useFetch from 'lib/hooks/useFetch'; -import { useRollbar } from 'lib/rollbar'; export default function useGetCsrfToken() { const nodeApiFetch = useFetch(); - const rollbar = useRollbar(); return useQuery({ queryKey: getResourceKey('csrf'), @@ -20,11 +18,13 @@ export default function useGetCsrfToken() { const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf'); if (!csrfFromHeader) { - rollbar?.warn('Client fetch failed', { - resource: 'csrf', - status_code: 500, - status_text: 'Unable to obtain csrf token from header', - }); + // I am not sure should we log this error or not + // so I commented it out for now + // rollbar?.warn('Client fetch failed', { + // resource: 'csrf', + // status_code: 500, + // status_text: 'Unable to obtain csrf token from header', + // }); return; } diff --git a/lib/metadata/__snapshots__/generate.test.ts.snap b/lib/metadata/__snapshots__/generate.test.ts.snap index 6997bf3de1..7c18d32400 100644 --- a/lib/metadata/__snapshots__/generate.test.ts.snap +++ b/lib/metadata/__snapshots__/generate.test.ts.snap @@ -32,7 +32,7 @@ exports[`generates correct metadata for: static route 1`] = ` "description": "Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the Blockscout (Blockscout) Explorer network.", "opengraph": { "description": "", - "imageUrl": "http://localhost:3000/static/og_placeholder.png", + "imageUrl": "http://localhost:3000/static/og_image.png", "title": "Blockscout transactions - Blockscout explorer | Blockscout", }, "title": "Blockscout transactions - Blockscout explorer | Blockscout", diff --git a/lib/rollbar/index.tsx b/lib/rollbar/index.tsx index 9dfbb887a0..32bf427adb 100644 --- a/lib/rollbar/index.tsx +++ b/lib/rollbar/index.tsx @@ -3,6 +3,10 @@ import type React from 'react'; import type { Configuration } from 'rollbar'; import config from 'configs/app'; +import { ABSENT_PARAM_ERROR_MESSAGE } from 'lib/errors/throwOnAbsentParamError'; +import { RESOURCE_LOAD_ERROR_MESSAGE } from 'lib/errors/throwOnResourceLoadError'; + +import { isBot, isHeadlessBrowser, isNextJsChunkError, getRequestInfo } from './utils'; const feature = config.features.rollbar; @@ -20,4 +24,42 @@ export const clientConfig: Configuration | undefined = feature.isEnabled ? { code_version: feature.codeVersion, app_instance: feature.instance, }, + checkIgnore(isUncaught, args, item) { + if (isBot(window.navigator.userAgent)) { + return true; + } + + if (isHeadlessBrowser(window.navigator.userAgent)) { + return true; + } + + if (isNextJsChunkError(getRequestInfo(item)?.url)) { + return true; + } + + return false; + }, + hostSafeList: [ config.app.host ].filter(Boolean), + ignoredMessages: [ + // these are React errors - "NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node." + // they could be caused by browser extensions + // one of the examples - https://github.com/facebook/react/issues/11538 + // we can ignore them for now + 'NotFoundError', + + // these are errors that we throw on when make a call to the API + RESOURCE_LOAD_ERROR_MESSAGE, + ABSENT_PARAM_ERROR_MESSAGE, + + // Filter out network-related errors that are usually not actionable + 'Network Error', + 'Failed to fetch', + + // Filter out CORS errors from third-party extensions + 'cross-origin', + + // Filter out client-side navigation cancellations + 'cancelled navigation', + ], + maxItems: 10, // Max items per page load } : undefined; diff --git a/lib/rollbar/utils.ts b/lib/rollbar/utils.ts new file mode 100644 index 0000000000..6d959128e2 --- /dev/null +++ b/lib/rollbar/utils.ts @@ -0,0 +1,58 @@ +import type { Dictionary } from 'rollbar'; + +export function isBot(userAgent: string | undefined) { + if (!userAgent) return false; + + const botPatterns = [ + 'Googlebot', // Google + 'Baiduspider', // Baidu + 'bingbot', // Bing + 'YandexBot', // Yandex + 'DuckDuckBot', // DuckDuckGo + 'Slurp', // Yahoo + 'Applebot', // Apple + 'facebookexternalhit', // Facebook + 'Twitterbot', // Twitter + 'rogerbot', // Moz + 'Alexa', // Alexa + 'AhrefsBot', // Ahrefs + 'SemrushBot', // Semrush + 'spider', // Generic spiders + 'crawler', // Generic crawlers + ]; + + return botPatterns.some(pattern => + userAgent.toLowerCase().includes(pattern.toLowerCase()), + ); +} + +export function isHeadlessBrowser(userAgent: string | undefined) { + if (!userAgent) return false; + + if ( + userAgent.includes('headless') || + userAgent.includes('phantomjs') || + userAgent.includes('selenium') || + userAgent.includes('puppeteer') + ) { + return true; + } +} + +export function isNextJsChunkError(url: unknown) { + if (typeof url !== 'string') return false; + return url.includes('/_next/'); +} + +export function getRequestInfo(item: Dictionary): { url: string } | undefined { + if ( + !item.request || + item.request === null || + typeof item.request !== 'object' || + !('url' in item.request) || + typeof item.request.url !== 'string' + ) { + return undefined; + } + return { url: item.request.url }; +} diff --git a/lib/web3/currentChain.ts b/lib/web3/currentChain.ts index dd3892859f..bdb35cfd8d 100644 --- a/lib/web3/currentChain.ts +++ b/lib/web3/currentChain.ts @@ -12,7 +12,7 @@ const currentChain = { }, rpcUrls: { 'default': { - http: [ config.chain.rpcUrl ?? '' ], + http: config.chain.rpcUrls, }, }, blockExplorers: { diff --git a/lib/web3/useAddOrSwitchChain.tsx b/lib/web3/useAddOrSwitchChain.tsx index a15815c9e9..b4e533571b 100644 --- a/lib/web3/useAddOrSwitchChain.tsx +++ b/lib/web3/useAddOrSwitchChain.tsx @@ -37,7 +37,7 @@ export default function useAddOrSwitchChain() { symbol: config.chain.currency.symbol, decimals: config.chain.currency.decimals, }, - rpcUrls: [ config.chain.rpcUrl ], + rpcUrls: config.chain.rpcUrls, blockExplorerUrls: [ config.app.baseUrl ], } ] as never; // in wagmi types for wallet_addEthereumChain method is not provided diff --git a/lib/web3/wagmiConfig.ts b/lib/web3/wagmiConfig.ts index 387a9bdd16..f5e28b811e 100644 --- a/lib/web3/wagmiConfig.ts +++ b/lib/web3/wagmiConfig.ts @@ -1,5 +1,5 @@ import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; -import { http } from 'viem'; +import { fallback, http } from 'viem'; import { createConfig } from 'wagmi'; import config from 'configs/app'; @@ -13,7 +13,11 @@ const wagmi = (() => { const wagmiConfig = createConfig({ chains: [ currentChain ], transports: { - [currentChain.id]: http(config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc`), + [currentChain.id]: fallback( + config.chain.rpcUrls + .map((url) => http(url)) + .concat(http(`${ config.api.endpoint }/api/eth-rpc`)), + ), }, ssr: true, batch: { multicall: { wait: 100 } }, @@ -26,7 +30,7 @@ const wagmi = (() => { networks: chains, multiInjectedProviderDiscovery: true, transports: { - [currentChain.id]: http(), + [currentChain.id]: fallback(config.chain.rpcUrls.map((url) => http(url))), }, projectId: feature.walletConnect.projectId, ssr: true, diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index 2ba5f68f5d..ae0fbf7ca2 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -51,7 +51,7 @@ export function app(): CspDev.DirectiveDescriptor { getFeaturePayload(config.features.rewards)?.api.endpoint, // chain RPC server - config.chain.rpcUrl, + ...config.chain.rpcUrls, 'https://infragrid.v.network', // RPC providers // github (spec for api-docs page) diff --git a/package.json b/package.json index dd4b9301a7..d9212fb6ed 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "test:jest": "jest", "test:jest:watch": "jest --watch", "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh", + "og-image:generate:dev": "./tools/scripts/og-image-generator.dev.sh", + "sitemap:generate:dev": "./tools/scripts/sitemap-generator.dev.sh", "monitoring:prometheus:local": "docker run --name blockscout_prometheus -d -p 127.0.0.1:9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus", "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise" }, @@ -60,10 +62,10 @@ "@opentelemetry/sdk-node": "0.49.1", "@opentelemetry/sdk-trace-node": "1.22.0", "@opentelemetry/semantic-conventions": "1.22.0", - "@rollbar/react": "0.12.0-beta", - "@scure/base": "1.1.9", "@reown/appkit": "1.6.0", "@reown/appkit-adapter-wagmi": "1.6.0", + "@rollbar/react": "0.12.0-beta", + "@scure/base": "1.1.9", "@slise/embed-react": "^2.2.0", "@tanstack/react-query": "5.55.4", "@tanstack/react-query-devtools": "5.55.4", diff --git a/public/static/og_twitter.png b/public/static/og_twitter.png deleted file mode 100644 index 1d83cbc77b..0000000000 Binary files a/public/static/og_twitter.png and /dev/null differ diff --git a/theme/components/Skeleton.ts b/theme/components/Skeleton.ts index 65555c8c58..6648bf4368 100644 --- a/theme/components/Skeleton.ts +++ b/theme/components/Skeleton.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import { Skeleton as SkeletonComponent } from '@chakra-ui/react'; import { defineStyle, diff --git a/theme/yarn.lock b/theme/yarn.lock index 28a6a1400c..baa25d2dd7 100644 --- a/theme/yarn.lock +++ b/theme/yarn.lock @@ -319,9 +319,9 @@ commander@^2.20.0: integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" diff --git a/tools/preset-sync/index.ts b/tools/preset-sync/index.ts index 4a4c782bbc..07d87510b1 100755 --- a/tools/preset-sync/index.ts +++ b/tools/preset-sync/index.ts @@ -41,6 +41,7 @@ const LOCAL_ENVS = { const IGNORED_ENVS = [ 'NEXT_PUBLIC_GIT_COMMIT_SHA', 'NEXT_PUBLIC_GIT_TAG', + 'NEXT_PUBLIC_ICON_SPRITE_HASH', ]; function parseScriptArgs() { diff --git a/tools/scripts/og-image-generator.dev.sh b/tools/scripts/og-image-generator.dev.sh new file mode 100755 index 0000000000..a47f2ac536 --- /dev/null +++ b/tools/scripts/og-image-generator.dev.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# use this script for testing the og image generator + +config_file="./configs/envs/.env.zkevm" + +dotenv \ + -e $config_file \ + -- bash -c 'node ./deploy/scripts/og_image_generator.js' \ No newline at end of file diff --git a/tools/scripts/sitemap-generator.dev.sh b/tools/scripts/sitemap-generator.dev.sh new file mode 100755 index 0000000000..d873cc63a8 --- /dev/null +++ b/tools/scripts/sitemap-generator.dev.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +config_file="./configs/envs/.env.eth" + +if [ ! -f "$config_file" ]; then + echo "Error: File '$config_file' not found." + exit 1 +fi + +dotenv \ + -e $config_file \ + -- bash -c 'cd ./deploy/tools/sitemap-generator && yarn && yarn next-sitemap' \ No newline at end of file diff --git a/types/api/search.ts b/types/api/search.ts index 94b70c4f5a..22068c85d5 100644 --- a/types/api/search.ts +++ b/types/api/search.ts @@ -1,3 +1,4 @@ +import type * as bens from '@blockscout/bens-types'; import type { TokenType } from 'types/api/token'; export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract'; @@ -47,6 +48,7 @@ export interface SearchResultDomain { expiry_date?: string; name: string; names_count: number; + protocol?: bens.ProtocolInfo; }; } diff --git a/ui/address/AddressCsvExportLink.tsx b/ui/address/AddressCsvExportLink.tsx index 313de665c8..13e470497f 100644 --- a/ui/address/AddressCsvExportLink.tsx +++ b/ui/address/AddressCsvExportLink.tsx @@ -1,4 +1,4 @@ -import { chakra, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react'; +import { chakra, Tooltip, Hide, Flex } from '@chakra-ui/react'; import React from 'react'; import type { CsvExportParams } from 'types/client/address'; @@ -8,6 +8,7 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsMobile from 'lib/hooks/useIsMobile'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/links/LinkInternal'; diff --git a/ui/address/accountHistory/AddressAccountHistoryListItem.tsx b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx index 1770b2062f..55dc187e5a 100644 --- a/ui/address/accountHistory/AddressAccountHistoryListItem.tsx +++ b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx @@ -1,8 +1,9 @@ -import { Box, Flex, Skeleton, Text } from '@chakra-ui/react'; +import { Box, Flex, Text } from '@chakra-ui/react'; import React, { useMemo } from 'react'; import type { NovesResponseData } from 'types/api/noves'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/links/LinkInternal'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; diff --git a/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx index 16211ba8f2..23bae973c9 100644 --- a/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx +++ b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx @@ -1,8 +1,9 @@ -import { Td, Tr, Skeleton, Box } from '@chakra-ui/react'; +import { Td, Tr, Box } from '@chakra-ui/react'; import React, { useMemo } from 'react'; import type { NovesResponseData } from 'types/api/noves'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/links/LinkInternal'; import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; diff --git a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx index fc3d6f6050..c1cb7bd75e 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx @@ -1,4 +1,4 @@ -import { Text, Flex, Skeleton } from '@chakra-ui/react'; +import { Text, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -8,6 +8,7 @@ import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import { currencyUnits } from 'lib/units'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; diff --git a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx index 296eb00567..6427902394 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx @@ -1,4 +1,4 @@ -import { Td, Tr, Flex, Skeleton } from '@chakra-ui/react'; +import { Td, Tr, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -7,6 +7,7 @@ import type { Block } from 'types/api/block'; import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; diff --git a/ui/address/coinBalance/AddressCoinBalanceListItem.tsx b/ui/address/coinBalance/AddressCoinBalanceListItem.tsx index 4f9a21dc74..f138215be4 100644 --- a/ui/address/coinBalance/AddressCoinBalanceListItem.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceListItem.tsx @@ -1,4 +1,4 @@ -import { Text, Stat, StatHelpText, StatArrow, Flex, Skeleton } from '@chakra-ui/react'; +import { Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -6,6 +6,7 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; import { WEI, ZERO } from 'lib/consts'; import { currencyUnits } from 'lib/units'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; diff --git a/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx b/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx index eff93d79a2..a12ea53b7a 100644 --- a/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx @@ -1,10 +1,11 @@ -import { Td, Tr, Text, Stat, StatHelpText, StatArrow, Skeleton } from '@chakra-ui/react'; +import { Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; import { WEI, ZERO } from 'lib/consts'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; diff --git a/ui/address/contract/ContractCodeIdes.tsx b/ui/address/contract/ContractCodeIdes.tsx index fc06317043..0c8ca5cbf9 100644 --- a/ui/address/contract/ContractCodeIdes.tsx +++ b/ui/address/contract/ContractCodeIdes.tsx @@ -6,7 +6,6 @@ import { PopoverBody, PopoverContent, Image, - Skeleton, useDisclosure, useColorModeValue, } from '@chakra-ui/react'; @@ -14,6 +13,7 @@ import React from 'react'; import config from 'configs/app'; import Popover from 'ui/shared/chakra/Popover'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/links/LinkExternal'; diff --git a/ui/address/contract/ContractDetailsVerificationButton.tsx b/ui/address/contract/ContractDetailsVerificationButton.tsx index 129f251f02..0f23d96f54 100644 --- a/ui/address/contract/ContractDetailsVerificationButton.tsx +++ b/ui/address/contract/ContractDetailsVerificationButton.tsx @@ -1,8 +1,10 @@ -import { Button, Skeleton } from '@chakra-ui/react'; +import { Button } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; +import Skeleton from 'ui/shared/chakra/Skeleton'; + interface Props { isLoading: boolean; addressHash: string; diff --git a/ui/address/contract/ContractExternalLibraries.tsx b/ui/address/contract/ContractExternalLibraries.tsx index 83df015e2b..08a940c540 100644 --- a/ui/address/contract/ContractExternalLibraries.tsx +++ b/ui/address/contract/ContractExternalLibraries.tsx @@ -10,7 +10,6 @@ import { PopoverBody, PopoverContent, PopoverTrigger, - Skeleton, StackDivider, useDisclosure, VStack, @@ -22,6 +21,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract'; import useIsMobile from 'lib/hooks/useIsMobile'; import { apos } from 'lib/html-entities'; import Popover from 'ui/shared/chakra/Popover'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/address/contract/ContractSourceAddressSelector.tsx b/ui/address/contract/ContractSourceAddressSelector.tsx index aa61b96732..3e92d3374a 100644 --- a/ui/address/contract/ContractSourceAddressSelector.tsx +++ b/ui/address/contract/ContractSourceAddressSelector.tsx @@ -1,8 +1,9 @@ -import { chakra, Flex, Skeleton } from '@chakra-ui/react'; +import { chakra, Flex } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import LinkNewTab from 'ui/shared/links/LinkNewTab'; diff --git a/ui/address/contract/ContractSourceCode.tsx b/ui/address/contract/ContractSourceCode.tsx index 7a3be99133..e80fbbe16a 100644 --- a/ui/address/contract/ContractSourceCode.tsx +++ b/ui/address/contract/ContractSourceCode.tsx @@ -1,4 +1,4 @@ -import { Flex, Skeleton, Text, Tooltip } from '@chakra-ui/react'; +import { Flex, Text, Tooltip } from '@chakra-ui/react'; import React from 'react'; import type { SmartContract } from 'types/api/contract'; @@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract'; import { route } from 'nextjs-routes'; import formatLanguageName from 'lib/contracts/formatLanguageName'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import LinkInternal from 'ui/shared/links/LinkInternal'; import CodeEditor from 'ui/shared/monaco/CodeEditor'; diff --git a/ui/address/contract/alerts/ContractDetailsAlerts.tsx b/ui/address/contract/alerts/ContractDetailsAlerts.tsx index f701de2f02..4bf02c15a6 100644 --- a/ui/address/contract/alerts/ContractDetailsAlerts.tsx +++ b/ui/address/contract/alerts/ContractDetailsAlerts.tsx @@ -1,4 +1,4 @@ -import { chakra, Alert, Box, Flex, Skeleton } from '@chakra-ui/react'; +import { chakra, Alert, Box, Flex } from '@chakra-ui/react'; import type { Channel } from 'phoenix'; import React from 'react'; @@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract'; import { route } from 'nextjs-routes'; import useSocketMessage from 'lib/socket/useSocketMessage'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkInternal from 'ui/shared/links/LinkInternal'; diff --git a/ui/address/contract/info/ContractDetailsInfoItem.tsx b/ui/address/contract/info/ContractDetailsInfoItem.tsx index ab4e0f10ce..b07ff57bf7 100644 --- a/ui/address/contract/info/ContractDetailsInfoItem.tsx +++ b/ui/address/contract/info/ContractDetailsInfoItem.tsx @@ -1,6 +1,7 @@ -import { chakra, useColorModeValue, Flex, GridItem, Skeleton } from '@chakra-ui/react'; +import { chakra, useColorModeValue, Flex, GridItem } from '@chakra-ui/react'; import React from 'react'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Hint from 'ui/shared/Hint'; interface Props { diff --git a/ui/address/contract/methods/ContractConnectWallet.tsx b/ui/address/contract/methods/ContractConnectWallet.tsx index 21c24193c3..148b0115d9 100644 --- a/ui/address/contract/methods/ContractConnectWallet.tsx +++ b/ui/address/contract/methods/ContractConnectWallet.tsx @@ -1,9 +1,10 @@ -import { Alert, Button, Flex, Skeleton } from '@chakra-ui/react'; +import { Alert, Button, Flex } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import useWeb3Wallet from 'lib/web3/useWallet'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; interface Props { diff --git a/ui/address/contract/methods/ContractCustomAbiAlert.tsx b/ui/address/contract/methods/ContractCustomAbiAlert.tsx index 633aa43cb4..92fda1fdf0 100644 --- a/ui/address/contract/methods/ContractCustomAbiAlert.tsx +++ b/ui/address/contract/methods/ContractCustomAbiAlert.tsx @@ -1,6 +1,7 @@ -import { Alert, Skeleton } from '@chakra-ui/react'; +import { Alert } from '@chakra-ui/react'; import React from 'react'; +import Skeleton from 'ui/shared/chakra/Skeleton'; interface Props { isLoading?: boolean; } diff --git a/ui/address/contract/methods/ContractMethodsCustom.tsx b/ui/address/contract/methods/ContractMethodsCustom.tsx index 00203175c7..d2890c69df 100644 --- a/ui/address/contract/methods/ContractMethodsCustom.tsx +++ b/ui/address/contract/methods/ContractMethodsCustom.tsx @@ -1,4 +1,4 @@ -import { Button, Flex, Skeleton, useDisclosure } from '@chakra-ui/react'; +import { Button, Flex, useDisclosure } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; @@ -8,6 +8,7 @@ import type { SmartContract } from 'types/api/contract'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import getQueryParamString from 'lib/router/getQueryParamString'; import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; import AuthGuard from 'ui/snippets/auth/AuthGuard'; import useIsAuth from 'ui/snippets/auth/useIsAuth'; diff --git a/ui/address/contract/methods/form/ContractMethodFieldInput.tsx b/ui/address/contract/methods/form/ContractMethodFieldInput.tsx index 7869c1690b..13e8d44d0f 100644 --- a/ui/address/contract/methods/form/ContractMethodFieldInput.tsx +++ b/ui/address/contract/methods/form/ContractMethodFieldInput.tsx @@ -28,7 +28,9 @@ interface Props { } const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDisabled, isOptional: isOptionalProp, level }: Props) => { - const ref = React.useRef(null); + const ref = React.useRef(); + + const [ intPower, setIntPower ] = React.useState(18); const isNativeCoin = data.fieldType === 'native_coin'; const isOptional = isOptionalProp || isNativeCoin; @@ -46,6 +48,8 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64; + React.useImperativeHandle(field.ref, () => ref.current); + const handleChange = React.useCallback((event: React.ChangeEvent) => { const formattedValue = format(event.target.value); field.onChange(formattedValue); // data send back to hook form @@ -83,6 +87,42 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi setValue(name, newValue, { shouldValidate: true }); }, [ format, name, setValue ]); + const handlePaste = React.useCallback((event: React.ClipboardEvent) => { + if (!argTypeMatchInt || !hasMultiplyButton) { + return; + } + + const value = Number(event.clipboardData.getData('text')); + + if (Object.is(value, NaN)) { + return; + } + + const isFloat = Number.isFinite(value) && !Number.isInteger(value); + + if (!isFloat) { + return; + } + + event.preventDefault(); + + if (field.value) { + return; + } + + const newValue = value * 10 ** intPower; + const formattedValue = format(newValue.toString()); + + field.onChange(formattedValue); + setValue(name, formattedValue, { shouldValidate: true }); + window.setTimeout(() => { + // move cursor to the end of the input + // but we have to wait for the input to get the new value + const END_OF_INPUT = 100; + ref.current?.setSelectionRange(END_OF_INPUT, END_OF_INPUT); + }, 100); + }, [ argTypeMatchInt, hasMultiplyButton, intPower, format, field, setValue, name ]); + const error = fieldState.error; return ( @@ -107,9 +147,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi thousandSeparator: ' ', decimalScale: 0, allowNegative: !argTypeMatchInt.isUnsigned, + getInputRef: (element: HTMLInputElement) => { + ref.current = element; + }, } : {}) } - ref={ ref } + // as we use mutable ref, we have to cast it to React.LegacyRef to trick chakra and typescript + ref={ ref as React.LegacyRef | undefined } onChange={ handleChange } + onPaste={ handlePaste } required={ !isOptional } isInvalid={ Boolean(error) } placeholder={ data.type } @@ -148,7 +193,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi Max )) } - { hasMultiplyButton && } + { hasMultiplyButton && ( + + ) } { error && { error.message } } diff --git a/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx b/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx index 4bd208d43d..a8edadaf3a 100644 --- a/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx +++ b/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx @@ -20,10 +20,12 @@ import IconSvg from 'ui/shared/IconSvg'; interface Props { onClick: (power: number) => void; isDisabled?: boolean; + initialValue: number; + onChange: (power: number) => void; } -const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => { - const [ selectedOption, setSelectedOption ] = React.useState(18); +const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onChange }: Props) => { + const [ selectedOption, setSelectedOption ] = React.useState(initialValue); const [ customValue, setCustomValue ] = React.useState(); const { isOpen, onToggle, onClose } = useDisclosure(); @@ -35,13 +37,16 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => { setSelectedOption((prev) => prev === id ? undefined : id); setCustomValue(undefined); onClose(); + onChange(id); } - }, [ onClose ]); + }, [ onClose, onChange ]); const handleInputChange = React.useCallback((event: React.ChangeEvent) => { - setCustomValue(Number(event.target.value)); + const value = Number(event.target.value); + setCustomValue(value); setSelectedOption(undefined); - }, []); + onChange(value); + }, [ onChange ]); const value = selectedOption || customValue; diff --git a/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png index 88737d03d9..2c8d11cb45 100644 Binary files a/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_default_base-view-mobile-dark-mode-1.png index eac80f82af..0a67dbce0c 100644 Binary files a/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_default_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png index 30db58a461..839c434bd9 100644 Binary files a/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/address/contract/methods/form/__screenshots__/ContractMethodForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/details/AddressCounterItem.tsx b/ui/address/details/AddressCounterItem.tsx index 2354967e22..fd4695a37a 100644 --- a/ui/address/details/AddressCounterItem.tsx +++ b/ui/address/details/AddressCounterItem.tsx @@ -1,4 +1,3 @@ -import { Skeleton } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -8,6 +7,7 @@ import type { AddressCounters } from 'types/api/address'; import { route } from 'nextjs-routes'; import type { ResourceError } from 'lib/api/resources'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import LinkInternal from 'ui/shared/links/LinkInternal'; interface Props { diff --git a/ui/address/details/AddressNameInfo.tsx b/ui/address/details/AddressNameInfo.tsx index d9d8dc9122..46536c99b2 100644 --- a/ui/address/details/AddressNameInfo.tsx +++ b/ui/address/details/AddressNameInfo.tsx @@ -1,8 +1,8 @@ -import { Skeleton } from '@chakra-ui/react'; import React from 'react'; import type { Address } from 'types/api/address'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; diff --git a/ui/address/details/AddressNetWorth.tsx b/ui/address/details/AddressNetWorth.tsx index 7fea56048c..a00d9c38f5 100644 --- a/ui/address/details/AddressNetWorth.tsx +++ b/ui/address/details/AddressNetWorth.tsx @@ -1,4 +1,4 @@ -import { Skeleton, Text, Flex } from '@chakra-ui/react'; +import { Text, Flex } from '@chakra-ui/react'; import React from 'react'; import type { Address } from 'types/api/address'; @@ -6,6 +6,7 @@ import type { Address } from 'types/api/address'; import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; import * as mixpanel from 'lib/mixpanel/index'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import TextSeparator from 'ui/shared/TextSeparator'; import { getTokensTotalInfo } from '../utils/tokenUtils'; diff --git a/ui/address/details/AddressQrCode.tsx b/ui/address/details/AddressQrCode.tsx index 4a3f3970a0..210e731dda 100644 --- a/ui/address/details/AddressQrCode.tsx +++ b/ui/address/details/AddressQrCode.tsx @@ -12,7 +12,6 @@ import { useDisclosure, Tooltip, IconButton, - Skeleton, } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import QRCode from 'qrcode'; @@ -23,6 +22,7 @@ import type { Address as AddressType } from 'types/api/address'; import getPageType from 'lib/mixpanel/getPageType'; import * as mixpanel from 'lib/mixpanel/index'; import { useRollbar } from 'lib/rollbar'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/address/details/AddressSaveOnGas.tsx b/ui/address/details/AddressSaveOnGas.tsx index 3452de3beb..3290774b69 100644 --- a/ui/address/details/AddressSaveOnGas.tsx +++ b/ui/address/details/AddressSaveOnGas.tsx @@ -1,9 +1,10 @@ -import { Image, Skeleton } from '@chakra-ui/react'; +import { Image } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import React from 'react'; import * as v from 'valibot'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import LinkExternal from 'ui/shared/links/LinkExternal'; import TextSeparator from 'ui/shared/TextSeparator'; diff --git a/ui/address/ensDomains/AddressEnsDomains.tsx b/ui/address/ensDomains/AddressEnsDomains.tsx index c141ae706e..0f488cfa84 100644 --- a/ui/address/ensDomains/AddressEnsDomains.tsx +++ b/ui/address/ensDomains/AddressEnsDomains.tsx @@ -8,7 +8,6 @@ import { PopoverContent, PopoverTrigger, Show, - Skeleton, useDisclosure, chakra, } from '@chakra-ui/react'; @@ -23,6 +22,7 @@ import { route } from 'nextjs-routes'; import type { ResourceError } from 'lib/api/resources'; import dayjs from 'lib/date/dayjs'; import Popover from 'ui/shared/chakra/Popover'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/links/LinkInternal'; diff --git a/ui/address/epochRewards/AddressEpochRewardsListItem.tsx b/ui/address/epochRewards/AddressEpochRewardsListItem.tsx index 4d61904873..bf09fd8714 100644 --- a/ui/address/epochRewards/AddressEpochRewardsListItem.tsx +++ b/ui/address/epochRewards/AddressEpochRewardsListItem.tsx @@ -1,9 +1,9 @@ -import { Skeleton } from '@chakra-ui/react'; import React from 'react'; import type { AddressEpochRewardsItem } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; diff --git a/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx b/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx index ab7169121a..4f1985b40c 100644 --- a/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx +++ b/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx @@ -1,9 +1,10 @@ -import { Flex, Td, Tr, Text, Skeleton } from '@chakra-ui/react'; +import { Flex, Td, Tr, Text } from '@chakra-ui/react'; import React from 'react'; import type { AddressEpochRewardsItem } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; diff --git a/ui/address/internals/AddressIntTxsListItem.tsx b/ui/address/internals/AddressIntTxsListItem.tsx index df13c6dbee..9ea80ae411 100644 --- a/ui/address/internals/AddressIntTxsListItem.tsx +++ b/ui/address/internals/AddressIntTxsListItem.tsx @@ -1,4 +1,4 @@ -import { Flex, HStack, Skeleton } from '@chakra-ui/react'; +import { Flex, HStack } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -7,6 +7,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; import { currencyUnits } from 'lib/units'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; diff --git a/ui/address/internals/AddressIntTxsTableItem.tsx b/ui/address/internals/AddressIntTxsTableItem.tsx index 3bd6b694fa..14e55999e9 100644 --- a/ui/address/internals/AddressIntTxsTableItem.tsx +++ b/ui/address/internals/AddressIntTxsTableItem.tsx @@ -1,4 +1,4 @@ -import { Tr, Td, Box, Flex, Skeleton } from '@chakra-ui/react'; +import { Tr, Td, Box, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -6,6 +6,7 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; diff --git a/ui/address/mud/AddressMudTablesListItem.tsx b/ui/address/mud/AddressMudTablesListItem.tsx index e3e5aa74bd..33c479ca21 100644 --- a/ui/address/mud/AddressMudTablesListItem.tsx +++ b/ui/address/mud/AddressMudTablesListItem.tsx @@ -1,4 +1,4 @@ -import { Divider, Text, Skeleton, useBoolean, Flex, Link, VStack, chakra, Box, Grid, GridItem } from '@chakra-ui/react'; +import { Divider, Text, useBoolean, Flex, Link, VStack, chakra, Box, Grid, GridItem } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address'; import { route } from 'nextjs-routes'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import HashStringShorten from 'ui/shared/HashStringShorten'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/address/mud/AddressMudTablesTableItem.tsx b/ui/address/mud/AddressMudTablesTableItem.tsx index 66d72a2a48..4796d5d097 100644 --- a/ui/address/mud/AddressMudTablesTableItem.tsx +++ b/ui/address/mud/AddressMudTablesTableItem.tsx @@ -1,4 +1,4 @@ -import { Td, Tr, Text, Skeleton, useBoolean, Link, Table, VStack, chakra } from '@chakra-ui/react'; +import { Td, Tr, Text, useBoolean, Link, Table, VStack, chakra } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -6,6 +6,7 @@ import type { AddressMudTableItem } from 'types/api/address'; import { route } from 'nextjs-routes'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/links/LinkInternal'; diff --git a/ui/address/tokenSelect/TokenSelect.tsx b/ui/address/tokenSelect/TokenSelect.tsx index aba582a3fe..af8d6057a3 100644 --- a/ui/address/tokenSelect/TokenSelect.tsx +++ b/ui/address/tokenSelect/TokenSelect.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, IconButton, Skeleton, Tooltip } from '@chakra-ui/react'; +import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react'; import { useQueryClient, useIsFetching } from '@tanstack/react-query'; import { sumBy } from 'es-toolkit'; import NextLink from 'next/link'; @@ -11,6 +11,7 @@ import { getResourceKey } from 'lib/api/useApiQuery'; import useIsMobile from 'lib/hooks/useIsMobile'; import * as mixpanel from 'lib/mixpanel/index'; import getQueryParamString from 'lib/router/getQueryParamString'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import useFetchTokens from '../utils/useFetchTokens'; diff --git a/ui/address/tokenSelect/TokenSelectButton.tsx b/ui/address/tokenSelect/TokenSelectButton.tsx index 9662b20cc9..77398e6c63 100644 --- a/ui/address/tokenSelect/TokenSelectButton.tsx +++ b/ui/address/tokenSelect/TokenSelectButton.tsx @@ -1,10 +1,11 @@ -import { Box, Button, Skeleton, chakra, useColorModeValue } from '@chakra-ui/react'; +import { Box, Button, chakra, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; import type { FormattedData } from './types'; import { space } from 'lib/html-entities'; import * as mixpanel from 'lib/mixpanel/index'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import { getTokensTotalInfo } from '../utils/tokenUtils'; diff --git a/ui/address/tokens/AddressCollections.tsx b/ui/address/tokens/AddressCollections.tsx index 71dad66352..3ef99fc515 100644 --- a/ui/address/tokens/AddressCollections.tsx +++ b/ui/address/tokens/AddressCollections.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Text, Grid, HStack, Skeleton } from '@chakra-ui/react'; +import { Box, Flex, Text, Grid, HStack } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; @@ -6,6 +6,7 @@ import { route } from 'nextjs-routes'; import useIsMobile from 'lib/hooks/useIsMobile'; import { apos } from 'lib/html-entities'; import ActionBar from 'ui/shared/ActionBar'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import DataListDisplay from 'ui/shared/DataListDisplay'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import LinkInternal from 'ui/shared/links/LinkInternal'; diff --git a/ui/address/tokens/ERC20TokensListItem.tsx b/ui/address/tokens/ERC20TokensListItem.tsx index 91e76c4cd8..43bb92c04c 100644 --- a/ui/address/tokens/ERC20TokensListItem.tsx +++ b/ui/address/tokens/ERC20TokensListItem.tsx @@ -1,10 +1,11 @@ -import { Flex, HStack, Skeleton } from '@chakra-ui/react'; +import { Flex, HStack } from '@chakra-ui/react'; import React from 'react'; import type { AddressTokenBalance } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; diff --git a/ui/address/tokens/ERC20TokensTableItem.tsx b/ui/address/tokens/ERC20TokensTableItem.tsx index 8f1f491db9..84e7d1547a 100644 --- a/ui/address/tokens/ERC20TokensTableItem.tsx +++ b/ui/address/tokens/ERC20TokensTableItem.tsx @@ -1,10 +1,11 @@ -import { Tr, Td, Flex, Skeleton } from '@chakra-ui/react'; +import { Tr, Td, Flex } from '@chakra-ui/react'; import React from 'react'; import type { AddressTokenBalance } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; diff --git a/ui/address/tokens/NFTItem.tsx b/ui/address/tokens/NFTItem.tsx index 270da14fbb..ee95353a8d 100644 --- a/ui/address/tokens/NFTItem.tsx +++ b/ui/address/tokens/NFTItem.tsx @@ -1,4 +1,4 @@ -import { Tag, Flex, Text, Link, Skeleton, LightMode } from '@chakra-ui/react'; +import { Tag, Flex, Text, Link, LightMode } from '@chakra-ui/react'; import React from 'react'; import type { AddressNFT } from 'types/api/address'; @@ -7,6 +7,7 @@ import { route } from 'nextjs-routes'; import getCurrencyValue from 'lib/getCurrencyValue'; import { getTokenTypeName } from 'lib/token/tokenTypes'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import NftMedia from 'ui/shared/nft/NftMedia'; diff --git a/ui/address/tokens/TokenBalancesItem.tsx b/ui/address/tokens/TokenBalancesItem.tsx index 0e1a2a3077..c733f1529c 100644 --- a/ui/address/tokens/TokenBalancesItem.tsx +++ b/ui/address/tokens/TokenBalancesItem.tsx @@ -1,6 +1,7 @@ -import { Box, Flex, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex, Text, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; +import Skeleton from 'ui/shared/chakra/Skeleton'; type Props = { name: string; value: string; diff --git a/ui/address/utils/useAddressQuery.ts b/ui/address/utils/useAddressQuery.ts index 391210a78c..9fa687e18a 100644 --- a/ui/address/utils/useAddressQuery.ts +++ b/ui/address/utils/useAddressQuery.ts @@ -118,8 +118,10 @@ export default function useAddressQuery({ hash, isEnabled = true }: Params): Add }, [ rpcQuery.data, rpcQuery.isPlaceholderData ]); const isRpcQuery = Boolean( + (apiQuery.isError || apiQuery.isPlaceholderData) && !(apiQuery.error?.status && NO_RPC_FALLBACK_ERROR_CODES.includes(apiQuery.error?.status)) && + !NO_RPC_FALLBACK_ERROR_CODES.includes(apiQuery.error?.status ?? 999) && apiQuery.errorUpdateCount > 0 && rpcQuery.data && publicClient, diff --git a/ui/addresses/AddressesListItem.tsx b/ui/addresses/AddressesListItem.tsx index 9ed744454e..f9b845a70d 100644 --- a/ui/addresses/AddressesListItem.tsx +++ b/ui/addresses/AddressesListItem.tsx @@ -1,4 +1,4 @@ -import { Flex, HStack, Skeleton } from '@chakra-ui/react'; +import { Flex, HStack } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -7,6 +7,7 @@ import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; import { ZERO } from 'lib/consts'; import { currencyUnits } from 'lib/units'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; diff --git a/ui/addresses/AddressesTableItem.tsx b/ui/addresses/AddressesTableItem.tsx index 18ecaa1afc..4bad73729c 100644 --- a/ui/addresses/AddressesTableItem.tsx +++ b/ui/addresses/AddressesTableItem.tsx @@ -1,10 +1,11 @@ -import { Tr, Td, Text, Skeleton, Flex } from '@chakra-ui/react'; +import { Tr, Td, Text, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; diff --git a/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx b/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx index b0cabf576c..988773cb2d 100644 --- a/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx +++ b/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx @@ -1,4 +1,4 @@ -import { HStack, Skeleton } from '@chakra-ui/react'; +import { HStack } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -6,6 +6,7 @@ import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; import { currencyUnits } from 'lib/units'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; diff --git a/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx b/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx index f3985ae9a3..14a672df47 100644 --- a/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx +++ b/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx @@ -1,10 +1,11 @@ -import { Tr, Td, Text, Skeleton } from '@chakra-ui/react'; +import { Tr, Td, Text } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; type Props = { diff --git a/ui/advancedFilter/ItemByColumn.tsx b/ui/advancedFilter/ItemByColumn.tsx index c9906d158d..d2b63bce53 100644 --- a/ui/advancedFilter/ItemByColumn.tsx +++ b/ui/advancedFilter/ItemByColumn.tsx @@ -1,4 +1,4 @@ -import { Flex, Skeleton } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { AdvancedFilterResponseItem } from 'types/api/advancedFilter'; @@ -7,6 +7,7 @@ import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; import type { ColumnsIds } from 'ui/advancedFilter/constants'; import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; diff --git a/ui/blob/BlobData.tsx b/ui/blob/BlobData.tsx index cfa9a31615..b17142fba8 100644 --- a/ui/blob/BlobData.tsx +++ b/ui/blob/BlobData.tsx @@ -1,4 +1,4 @@ -import { Flex, GridItem, Skeleton, Button } from '@chakra-ui/react'; +import { Flex, GridItem, Button } from '@chakra-ui/react'; import React from 'react'; import * as blobUtils from 'lib/blob'; @@ -8,6 +8,7 @@ import downloadBlob from 'lib/downloadBlob'; import hexToBase64 from 'lib/hexToBase64'; import hexToBytes from 'lib/hexToBytes'; import hexToUtf8 from 'lib/hexToUtf8'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; import Select from 'ui/shared/select/Select'; diff --git a/ui/blob/BlobInfo.tsx b/ui/blob/BlobInfo.tsx index cf746d6ed2..ea14bb374e 100644 --- a/ui/blob/BlobInfo.tsx +++ b/ui/blob/BlobInfo.tsx @@ -1,8 +1,9 @@ -import { Alert, Grid, GridItem, Skeleton } from '@chakra-ui/react'; +import { Alert, Grid, GridItem } from '@chakra-ui/react'; import React from 'react'; import type { Blob } from 'types/api/blobs'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index 21621cb9b9..f97d84d250 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -1,4 +1,4 @@ -import { Grid, GridItem, Text, Link, Box, Tooltip, Skeleton } from '@chakra-ui/react'; +import { Grid, GridItem, Text, Link, Box, Tooltip } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; @@ -20,6 +20,7 @@ import getQueryParamString from 'lib/router/getQueryParamString'; import { currencyUnits } from 'lib/units'; import OptimisticL2TxnBatchDA from 'ui/shared/batch/OptimisticL2TxnBatchDA'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; diff --git a/ui/block/epochRewards/BlockEpochElectionRewardsListItem.tsx b/ui/block/epochRewards/BlockEpochElectionRewardsListItem.tsx index d43cd48018..aae73d82bd 100644 --- a/ui/block/epochRewards/BlockEpochElectionRewardsListItem.tsx +++ b/ui/block/epochRewards/BlockEpochElectionRewardsListItem.tsx @@ -1,9 +1,10 @@ -import { Box, Flex, IconButton, Skeleton, useDisclosure } from '@chakra-ui/react'; +import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react'; import React from 'react'; import type { BlockEpoch, BlockEpochElectionReward } from 'types/api/block'; import getCurrencyValue from 'lib/getCurrencyValue'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EpochRewardTypeTag from 'ui/shared/EpochRewardTypeTag'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/block/epochRewards/BlockEpochElectionRewardsTableItem.tsx b/ui/block/epochRewards/BlockEpochElectionRewardsTableItem.tsx index b12cb6dea1..5dbcca6ec3 100644 --- a/ui/block/epochRewards/BlockEpochElectionRewardsTableItem.tsx +++ b/ui/block/epochRewards/BlockEpochElectionRewardsTableItem.tsx @@ -1,9 +1,10 @@ -import { Flex, IconButton, Skeleton, Td, Tr, useDisclosure } from '@chakra-ui/react'; +import { Flex, IconButton, Td, Tr, useDisclosure } from '@chakra-ui/react'; import React from 'react'; import type { BlockEpoch, BlockEpochElectionReward } from 'types/api/block'; import getCurrencyValue from 'lib/getCurrencyValue'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EpochRewardTypeTag from 'ui/shared/EpochRewardTypeTag'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/blocks/BlocksListItem.tsx b/ui/blocks/BlocksListItem.tsx index 44864e5a65..be78336f86 100644 --- a/ui/blocks/BlocksListItem.tsx +++ b/ui/blocks/BlocksListItem.tsx @@ -1,4 +1,4 @@ -import { Flex, Skeleton, Text, Box, Tooltip } from '@chakra-ui/react'; +import { Flex, Text, Box, Tooltip } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import { capitalize } from 'es-toolkit'; import React from 'react'; @@ -13,6 +13,7 @@ import { WEI } from 'lib/consts'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import { currencyUnits } from 'lib/units'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/blocks/BlocksTabSlot.tsx b/ui/blocks/BlocksTabSlot.tsx index ff8b9fe6d8..b6a98ca6dd 100644 --- a/ui/blocks/BlocksTabSlot.tsx +++ b/ui/blocks/BlocksTabSlot.tsx @@ -1,4 +1,4 @@ -import { Flex, Box, Text, Skeleton } from '@chakra-ui/react'; +import { Flex, Box, Text } from '@chakra-ui/react'; import React from 'react'; import type { PaginationParams } from 'ui/shared/pagination/types'; @@ -8,6 +8,7 @@ import { route } from 'nextjs-routes'; import useApiQuery from 'lib/api/useApiQuery'; import { nbsp } from 'lib/html-entities'; import { HOMEPAGE_STATS } from 'stubs/stats'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/links/LinkInternal'; import Pagination from 'ui/shared/pagination/Pagination'; diff --git a/ui/blocks/BlocksTableItem.tsx b/ui/blocks/BlocksTableItem.tsx index 48114e1ea5..c5bf924e10 100644 --- a/ui/blocks/BlocksTableItem.tsx +++ b/ui/blocks/BlocksTableItem.tsx @@ -1,4 +1,4 @@ -import { Tr, Td, Flex, Box, Tooltip, Skeleton, useColorModeValue } from '@chakra-ui/react'; +import { Tr, Td, Flex, Box, Tooltip, useColorModeValue } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import { motion } from 'framer-motion'; import React from 'react'; @@ -11,6 +11,7 @@ import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import { WEI } from 'lib/consts'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import IconSvg from 'ui/shared/IconSvg'; diff --git a/ui/contractVerification/methods/ContractVerificationSolidityFoundry.tsx b/ui/contractVerification/methods/ContractVerificationSolidityFoundry.tsx index a481631c3c..d91ca04e24 100644 --- a/ui/contractVerification/methods/ContractVerificationSolidityFoundry.tsx +++ b/ui/contractVerification/methods/ContractVerificationSolidityFoundry.tsx @@ -15,7 +15,7 @@ const ContractVerificationSolidityFoundry = () => { const address = watch('address'); const codeSnippet = `forge verify-contract \\ - --rpc-url ${ config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc` } \\ + --rpc-url ${ config.chain.rpcUrls[0] || `${ config.api.endpoint }/api/eth-rpc` } \\ --verifier blockscout \\ --verifier-url '${ config.api.endpoint }/api/' \\ ${ address || '
' } \\ diff --git a/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx b/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx index d779aea9e4..9dda8e2e25 100644 --- a/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx +++ b/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx @@ -22,7 +22,7 @@ const ContractVerificationSolidityHardhat = ({ config: formConfig }: { config: S solidity: "${ latestSolidityVersion || '0.8.24' }", // replace if necessary networks: { '${ chainNameSlug }': { - url: '${ config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc` }' + url: '${ config.chain.rpcUrls[0] || `${ config.api.endpoint }/api/eth-rpc` }' }, }, etherscan: { diff --git a/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx b/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx index 764ff7999a..b0a92b8126 100644 --- a/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx +++ b/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx @@ -1,8 +1,9 @@ -import { Box, Skeleton } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { CustomAbi } from 'types/api/account'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; diff --git a/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx b/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx index 121c28eaac..b3711a9d26 100644 --- a/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx +++ b/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx @@ -2,12 +2,12 @@ import { Tr, Td, Box, - Skeleton, } from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { CustomAbi } from 'types/api/account'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; diff --git a/ui/deposits/optimisticL2/OptimisticDepositsListItem.tsx b/ui/deposits/optimisticL2/OptimisticDepositsListItem.tsx index 7c1564735d..87f1936e7c 100644 --- a/ui/deposits/optimisticL2/OptimisticDepositsListItem.tsx +++ b/ui/deposits/optimisticL2/OptimisticDepositsListItem.tsx @@ -1,10 +1,10 @@ -import { Skeleton } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntityL1 from 'ui/shared/entities/address/AddressEntityL1'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; diff --git a/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx b/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx index 299509a4a9..b76a972bac 100644 --- a/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx +++ b/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx @@ -1,10 +1,11 @@ -import { Td, Tr, Skeleton } from '@chakra-ui/react'; +import { Td, Tr } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntityL1 from 'ui/shared/entities/address/AddressEntityL1'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; diff --git a/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx b/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx index 37a6d20602..0c4ea8099e 100644 --- a/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx +++ b/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx @@ -1,10 +1,11 @@ -import { Skeleton, chakra } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; import type { ScrollL2MessageItem } from 'types/api/scrollL2'; import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; diff --git a/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx b/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx index 4a48813c23..5ec1bbb355 100644 --- a/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx +++ b/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx @@ -1,10 +1,11 @@ -import { Td, Tr, Skeleton, chakra } from '@chakra-ui/react'; +import { Td, Tr, chakra } from '@chakra-ui/react'; import React from 'react'; import type { ScrollL2MessageItem } from 'types/api/scrollL2'; import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; diff --git a/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx b/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx index 3bca44ee29..0a9de9a39e 100644 --- a/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx +++ b/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx @@ -1,10 +1,11 @@ -import { Skeleton, chakra } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { ZkEvmL2DepositsItem } from 'types/api/zkEvmL2'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; diff --git a/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx b/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx index 962135741b..62a52ac14f 100644 --- a/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx +++ b/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx @@ -1,10 +1,11 @@ -import { Td, Tr, Skeleton, chakra } from '@chakra-ui/react'; +import { Td, Tr, chakra } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { ZkEvmL2DepositsItem } from 'types/api/zkEvmL2'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; diff --git a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx index 27d83573c5..8080006e60 100644 --- a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx +++ b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx @@ -1,9 +1,9 @@ -import { Skeleton } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2'; import HashStringShorten from 'ui/shared/HashStringShorten'; diff --git a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx index dd55542b5d..0b4d6a5759 100644 --- a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx +++ b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx @@ -1,9 +1,10 @@ -import { Flex, Td, Tr, Skeleton } from '@chakra-ui/react'; +import { Flex, Td, Tr } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2'; import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2'; import HashStringShorten from 'ui/shared/HashStringShorten'; diff --git a/ui/games/CapybaraRunner.tsx b/ui/games/CapybaraRunner.tsx index dc23d40de2..11d01405b6 100644 --- a/ui/games/CapybaraRunner.tsx +++ b/ui/games/CapybaraRunner.tsx @@ -4,12 +4,14 @@ import Script from 'next/script'; import React from 'react'; import config from 'configs/app'; - +import useIsMobile from 'lib/hooks/useIsMobile'; const easterEggBadgeFeature = config.features.easterEggBadge; const CapybaraRunner = () => { const [ hasReachedHighScore, setHasReachedHighScore ] = React.useState(false); + const isMobile = useIsMobile(); + React.useEffect(() => { const preventDefaultKeys = (e: KeyboardEvent) => { if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'ArrowDown') { @@ -32,6 +34,8 @@ const CapybaraRunner = () => { return ( <> + Score 1000 to win a special prize! + { isMobile ? 'Tap below to start' : 'Press space to start' }