Merge remote-tracking branch 'upstream/main'

This commit is contained in:
2025-07-12 13:24:11 -05:00
296 changed files with 6009 additions and 969 deletions
+24
View File
@@ -45,6 +45,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Run ESLint
run: yarn lint:eslint
@@ -77,6 +81,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Install package dependencies
run: |
cd ./toolkit/package
@@ -136,6 +144,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Install script dependencies
run: cd ./deploy/tools/envs-validator && yarn --frozen-lockfile
@@ -175,6 +187,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Run Jest
run: yarn test:jest ${{ github.event_name == 'pull_request' && '--changedSince=origin/main' || '' }} --passWithNoTests
@@ -207,6 +223,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Install script dependencies
run: cd ./deploy/tools/affected-tests && yarn --frozen-lockfile
@@ -264,6 +284,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Download affected tests list
if: ${{ needs.pw_affected_tests.result == 'success' }}
uses: actions/download-artifact@v4
+1
View File
@@ -25,6 +25,7 @@ on:
- neon_devnet
- optimism
- optimism_sepolia
- optimism_superchain
- polygon
- rootstock
- scroll_sepolia
+2
View File
@@ -27,12 +27,14 @@ on:
- neon_devnet
- optimism
- optimism_sepolia
- optimism_superchain
- polygon
- rari_testnet
- rootstock
- shibarium
- scroll_sepolia
- stability
- tac
- tac_turin
- zkevm
- zilliqa_prototestnet
+4
View File
@@ -37,6 +37,10 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Generate Chakra types
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Make production build with source maps
run: yarn build
env:
+1
View File
@@ -14,6 +14,7 @@
/out/
/public/assets/envs.js
/public/assets/configs
/public/assets/multichain
/public/icons/sprite.svg
/public/icons/sprite.*.svg
/public/icons/registry.json
+2
View File
@@ -377,12 +377,14 @@
"optimism",
"optimism_interop_0",
"optimism_sepolia",
"optimism_superchain",
"polygon",
"rari_testnet",
"rootstock_testnet",
"scroll_sepolia",
"shibarium",
"stability_testnet",
"tac",
"tac_turin",
"zkevm",
"zilliqa_prototestnet",
+14
View File
@@ -45,6 +45,12 @@ WORKDIR /sitemap-generator
COPY ./deploy/tools/sitemap-generator/package.json ./deploy/tools/sitemap-generator/yarn.lock ./
RUN yarn --frozen-lockfile --network-timeout 100000
### MULTICHAIN CONFIG GENERATOR
# Install dependencies
WORKDIR /multichain-config-generator
COPY ./deploy/tools/multichain-config-generator/package.json ./deploy/tools/multichain-config-generator/yarn.lock ./
RUN yarn --frozen-lockfile --network-timeout 100000
# *****************************
# ****** STAGE 2: Build *******
@@ -106,6 +112,11 @@ COPY --from=deps /favicon-generator/node_modules ./deploy/tools/favicon-generato
# Copy dependencies and source code
COPY --from=deps /sitemap-generator/node_modules ./deploy/tools/sitemap-generator/node_modules
### MULTICHAIN CONFIG GENERATOR
# Copy dependencies and source code, then build
COPY --from=deps /multichain-config-generator/node_modules ./deploy/tools/multichain-config-generator/node_modules
RUN cd ./deploy/tools/multichain-config-generator && yarn build
# *****************************
# ******* STAGE 3: Run ********
@@ -130,8 +141,11 @@ RUN chown nextjs:nodejs .next
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Copy tools
COPY --from=builder /app/deploy/tools/envs-validator/index.js ./envs-validator.js
COPY --from=builder /app/deploy/tools/feature-reporter/index.js ./feature-reporter.js
COPY --from=builder /app/deploy/tools/multichain-config-generator/dist ./deploy/tools/multichain-config-generator/dist
# Copy scripts
## Entripoint
+22 -1
View File
@@ -7,6 +7,7 @@ import { getEnvValue } from './utils';
export interface ApiPropsBase {
endpoint: string;
basePath?: string;
socketEndpoint?: string;
}
export interface ApiPropsFull extends ApiPropsBase {
@@ -100,6 +101,25 @@ const rewardsApi = (() => {
});
})();
const multichainApi = (() => {
const apiHost = getEnvValue('NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST');
if (!apiHost) {
return;
}
try {
const url = new URL(apiHost);
return Object.freeze({
endpoint: apiHost,
socketEndpoint: `wss://${ url.host }`,
});
} catch (error) {
return;
}
})();
const statsApi = (() => {
const apiHost = getEnvValue('NEXT_PUBLIC_STATS_API_HOST');
if (!apiHost) {
@@ -135,7 +155,7 @@ const visualizeApi = (() => {
});
})();
type Apis = {
export type Apis = {
general: ApiPropsFull;
} & Partial<Record<Exclude<ApiName, 'general'>, ApiPropsBase>>;
@@ -145,6 +165,7 @@ const apis: Apis = Object.freeze({
bens: bensApi,
contractInfo: contractInfoApi,
metadata: metadataApi,
multichain: multichainApi,
rewards: rewardsApi,
stats: statsApi,
tac: tacApi,
@@ -0,0 +1,27 @@
import type { Feature } from './types';
import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from '../utils';
// config file will be downloaded at run-time and saved in the public folder
const widgets = parseEnvJson<Array<string>>(getEnvValue('NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS'));
const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL');
const title = 'Address 3rd party widgets';
const config: Feature<{ widgets: Array<string>; configUrl: string }> = (() => {
if (widgets && widgets.length > 0 && configUrl) {
return Object.freeze({
title,
isEnabled: true,
widgets,
configUrl,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
+3 -1
View File
@@ -1,14 +1,16 @@
import type { Feature } from './types';
import apis from '../apis';
import { getEnvValue } from '../utils';
const title = 'Address metadata';
const config: Feature<{}> = (() => {
const config: Feature<{ isAddressTagsUpdateEnabled: boolean }> = (() => {
if (apis.metadata) {
return Object.freeze({
title,
isEnabled: true,
isAddressTagsUpdateEnabled: getEnvValue('NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED') !== 'false',
});
}
+11 -4
View File
@@ -2,6 +2,7 @@ import type { Feature } from './types';
import chain from '../chain';
import { getEnvValue } from '../utils';
import opSuperchain from './opSuperchain';
const walletConnectProjectId = getEnvValue('NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID');
@@ -9,15 +10,21 @@ const title = 'Blockchain interaction (writing to contract, etc.)';
const config: Feature<{ walletConnect: { projectId: string } }> = (() => {
if (
// all chain parameters are required for wagmi provider
// @wagmi/chains/dist/index.d.ts
// all chain parameters are required for wagmi provider
// @wagmi/chains/dist/index.d.ts
const isSingleChain = Boolean(
chain.id &&
chain.name &&
chain.currency.name &&
chain.currency.symbol &&
chain.currency.decimals &&
chain.rpcUrls.length > 0 &&
chain.rpcUrls.length > 0,
);
const isOpSuperchain = opSuperchain.isEnabled;
if (
(isSingleChain || isOpSuperchain) &&
walletConnectProjectId
) {
return Object.freeze({
+1 -3
View File
@@ -4,14 +4,12 @@ import { getEnvValue } from '../utils';
const title = 'Celo chain';
const config: Feature<{ L2UpgradeBlock: number | undefined; BLOCKS_PER_EPOCH: number }> = (() => {
const config: Feature<{ }> = (() => {
if (getEnvValue('NEXT_PUBLIC_CELO_ENABLED') === 'true') {
return Object.freeze({
title,
isEnabled: true,
L2UpgradeBlock: getEnvValue('NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK') ? Number(getEnvValue('NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK')) : undefined,
BLOCKS_PER_EPOCH: 17_280,
});
}
+2
View File
@@ -2,6 +2,7 @@ export { default as advancedFilter } from './advancedFilter';
export { default as account } from './account';
export { default as addressVerification } from './addressVerification';
export { default as addressMetadata } from './addressMetadata';
export { default as address3rdPartyWidgets } from './address3rdPartyWidgets';
export { default as adsBanner } from './adsBanner';
export { default as adsText } from './adsText';
export { default as beaconChain } from './beaconChain';
@@ -26,6 +27,7 @@ export { default as mixpanel } from './mixpanel';
export { default as mudFramework } from './mudFramework';
export { default as multichainButton } from './multichainButton';
export { default as nameService } from './nameService';
export { default as opSuperchain } from './opSuperchain';
export { default as pools } from './pools';
export { default as publicTagsSubmission } from './publicTagsSubmission';
export { default as restApiDocs } from './restApiDocs';
+24
View File
@@ -0,0 +1,24 @@
import type { Feature } from './types';
import apis from '../apis';
import { getEnvValue } from '../utils';
const isEnabled = getEnvValue('NEXT_PUBLIC_OP_SUPERCHAIN_ENABLED') === 'true';
const title = 'OP Superchain interop explorer';
const config: Feature<{ }> = (() => {
if (apis.multichain && isEnabled) {
return Object.freeze({
title,
isEnabled: true,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
+1 -1
View File
@@ -15,7 +15,6 @@ NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=celo-alfajores.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CELO_ENABLED=true
NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK=26369280
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/celo.json
@@ -24,6 +23,7 @@ NEXT_PUBLIC_GAS_TRACKER_ENABLED=false
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x9767ce30754afad2a3279b9df2d13257f467c3dad4e0e601271e66d16dfd1641
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','current_epoch']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(252, 255, 82, 1)'],'text_color':['rgba(0, 0, 0, 1)']}
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
+3 -1
View File
@@ -68,4 +68,6 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'},{'name':'Rarible','collection_url':'https://rarible.com/collection/{hash}/items','instance_url':'https://rarible.com/token/{hash}:{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/rarible.png'},{'name':'Blur','collection_url':'https://blur.io/eth/collection/{hash}','instance_url':'https://blur.io/eth/asset/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/blur.png'},{'name':'MagicEden','collection_url':'https://magiceden.io/collections/ethereum/{hash}','instance_url':'https://magiceden.io/item-details/ethereum/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/magiceden.png'}]
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['talentprotocol', 'efp', 'webacy', 'deepdao', 'humanpassport', 'trustblock', 'bankless']
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/widgets/config.json
+2
View File
@@ -9,6 +9,8 @@ NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true
# Instance ENVs
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" }
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" }
+46
View File
@@ -0,0 +1,46 @@
# Set of ENVs for OP Mainnet network explorer
# https://xxx.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=optimism_superchain"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
# Instance ENVs
# TODO @tom2drum make these envs optional for multichain (adjust docs)
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=localhost
NEXT_PUBLIC_API_PORT=3001
NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_NETWORK_ID=10
# TODO @tom2drum New ENVs (add to docs)
NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST=https://multichain-aggregator.k8s-dev.blockscout.com
NEXT_PUBLIC_OP_SUPERCHAIN_ENABLED=true
# TODO @tom2drum remove this
SKIP_ENVS_VALIDATION=true
NEXT_PUBLIC_API_SPEC_URL=none
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/optimism-mainnet.json
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap', 'secondary_coin_price']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%)'],'text_color':['rgb(255, 255, 255)']}
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-mainnet-light.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-mainnet-dark.svg
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_NETWORK_NAME=OP Superchain
NEXT_PUBLIC_NETWORK_SHORT_NAME=OP Superchain
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/optimism-mainnet.png
NEXT_PUBLIC_GAS_TRACKER_ENABLED=false
NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS=['eth_rpc_api','rpc_api']
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=false
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_USE_NEXT_JS_PROXY=false
+45
View File
@@ -0,0 +1,45 @@
# Set of ENVs for TAC network explorer
# https://explorer.tac.build
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=tac"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=explorer.tac.build
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/tac.json
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x9e2386a15e263fffc7b481ae1a6b8d71aa94b87c7683b3f2d756bb912622d638
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['no-repeat center/100% auto url(https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-skins/tac.jpg)'],'text_color':['rgba(242,235,255,1)'],'button':{'_default':{'background':['rgba(30,23,44,1)']},'_hover':{'background':['rgba(66,14,70,1)']}}}
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=TAC
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=TAC
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/tac-light.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/tac-dark.svg
NEXT_PUBLIC_NETWORK_ID=239
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/tac-light.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/tac-dark.svg
NEXT_PUBLIC_NETWORK_NAME=TAC
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/tac
NEXT_PUBLIC_NETWORK_SHORT_NAME=TAC
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/tac-turin.png
NEXT_PUBLIC_STATS_API_BASE_PATH=/stats-service
NEXT_PUBLIC_STATS_API_HOST=https://explorer.tac.build
NEXT_PUBLIC_TAC_OPERATION_LIFECYCLE_API_HOST=https://tac-operation-lifecycle.k8s.blockscout.com
NEXT_PUBLIC_TAC_TON_EXPLORER_URL=https://tonviewer.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
+48
View File
@@ -0,0 +1,48 @@
/* eslint-disable no-restricted-properties */
import type { MultichainConfig } from 'types/multichain';
import config from 'configs/app';
function isRunningInDocker() {
return process.env.HOSTNAME !== undefined;
}
let value: MultichainConfig | undefined = undefined;
async function fetchConfig() {
if (process.env.NEXT_RUNTIME !== 'edge') {
throw new Error('NEXT_RUNTIME is not edge');
}
// In edge runtime, we need to use absolute URLs
// When in Docker, use the internal hostname
const baseUrl = isRunningInDocker() ?
`http://${ process.env.HOSTNAME }:${ config.app.port || 3000 }` :
config.app.baseUrl;
const url = baseUrl + '/assets/multichain/config.json';
const response = await fetch(url);
const json = await response.json();
value = json as MultichainConfig;
return value;
}
export async function load() {
if (!value) {
return new Promise<MultichainConfig | undefined>((resolve, reject) => {
fetchConfig()
.then((value) => {
resolve(value);
}).catch((error) => {
reject(error);
});
});
}
return Promise.resolve(value);
}
export function getValue() {
return value;
}
+43
View File
@@ -0,0 +1,43 @@
import type { MultichainConfig } from 'types/multichain';
let value: MultichainConfig | undefined = undefined;
function readFileConfig() {
// eslint-disable-next-line no-restricted-properties
if (process.env.NEXT_RUNTIME !== 'nodejs') {
throw new Error('NEXT_RUNTIME is not nodejs');
}
try {
const path = require('path');
const { readFileSync } = require('fs');
const publicFolder = path.resolve('public');
const configPath = path.resolve(publicFolder, 'assets/multichain/config.json');
const config = readFileSync(configPath, 'utf8');
value = JSON.parse(config) as MultichainConfig;
return value;
} catch (error) {
return;
}
}
export async function load() {
if (!value) {
return new Promise<MultichainConfig | undefined>((resolve) => {
const value = readFileConfig();
resolve(value);
});
}
return Promise.resolve(value);
}
export function getValue() {
if (!value) {
return readFileConfig();
}
return value;
}
+19
View File
@@ -0,0 +1,19 @@
import type { MultichainConfig } from 'types/multichain';
import config from 'configs/app';
import * as multichainConfigNodejs from 'configs/multichain/config.nodejs';
import { isBrowser } from 'toolkit/utils/isBrowser';
const multichainConfig: () => MultichainConfig | undefined = () => {
if (!config.features.opSuperchain.isEnabled) {
return;
}
if (isBrowser()) {
return window.__multichainConfig;
}
return multichainConfigNodejs.getValue();
};
export default multichainConfig;
+1
View File
@@ -26,6 +26,7 @@ ASSETS_ENVS=(
"NEXT_PUBLIC_NETWORK_ICON"
"NEXT_PUBLIC_NETWORK_ICON_DARK"
"NEXT_PUBLIC_OG_IMAGE_URL"
"NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL"
)
# Create the assets directory if it doesn't exist
+3
View File
@@ -64,6 +64,9 @@ node --no-warnings ./og_image_generator.js
# Create envs.js file with run-time environment variables for the client app
./make_envs_script.sh
# Generate multichain config
node ./deploy/tools/multichain-config-generator/dist/index.js
# Generate sitemap.xml and robots.txt files
./sitemap_generator.sh
+1
View File
@@ -42,6 +42,7 @@ async function validateEnvs(appEnvs: Record<string, string>) {
'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL',
'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL',
'NEXT_PUBLIC_FOOTER_LINKS',
'NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL',
];
for await (const envName of envsWithJsonConfig) {
+71 -20
View File
@@ -35,8 +35,8 @@ import type { ChainIndicatorId, HeroBannerButtonState, HeroBannerConfig, HomeSta
import { type NetworkVerificationTypeEnvs, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks';
import { COLOR_THEME_IDS } from '../../../types/settings';
import type { FontFamily } from '../../../types/ui';
import type { AddressFormat, AddressViewId } from '../../../types/views/address';
import { ADDRESS_FORMATS, ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
import type { AddressFormat, AddressViewId, Address3rdPartyWidget } from '../../../types/views/address';
import { ADDRESS_FORMATS, ADDRESS_VIEWS_IDS, IDENTICON_TYPES, ADDRESS_3RD_PARTY_WIDGET_PAGES } from '../../../types/views/address';
import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
import type { BlockFieldId } from '../../../types/views/block';
import type { NftMarketplaceItem } from '../../../types/views/nft';
@@ -71,12 +71,12 @@ const urlTest: yup.TestConfig = {
exclusive: true,
};
const getYupValidationErrorMessage = (error: unknown) =>
typeof error === 'object' &&
error !== null &&
'errors' in error &&
Array.isArray(error.errors) ?
error.errors.join(', ') :
const getYupValidationErrorMessage = (error: unknown) =>
typeof error === 'object' &&
error !== null &&
'errors' in error &&
Array.isArray(error.errors) ?
error.errors.join(', ') :
'';
const marketplaceAppSchema: yup.ObjectSchema<MarketplaceAppOverview> = yup
@@ -448,16 +448,6 @@ const celoSchema = yup
.object()
.shape({
NEXT_PUBLIC_CELO_ENABLED: yup.boolean(),
NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK: yup
.string()
.when('NEXT_PUBLIC_CELO_ENABLED', {
is: (value: boolean) => value,
then: (schema) => schema.min(0).optional(),
otherwise: (schema) => schema.max(
-1,
'NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK cannot not be used if NEXT_PUBLIC_CELO_ENABLED is not set to "true"',
),
}),
});
const adButlerConfigSchema = yup
@@ -590,6 +580,7 @@ const footerLinkSchema: yup.ObjectSchema<CustomLink> = yup
.object({
text: yup.string().required(),
url: yup.string().test(urlTest).required(),
iconUrl: yup.array().of(yup.string().required().test(urlTest)),
});
const footerLinkGroupSchema: yup.ObjectSchema<CustomLinksGroup> = yup
@@ -666,6 +657,25 @@ const bridgedTokensSchema = yup
}),
});
const addressMetadataSchema = yup
.object()
.shape({
NEXT_PUBLIC_METADATA_SERVICE_API_HOST: yup
.string()
.test(urlTest),
NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED: yup
.boolean()
.when('NEXT_PUBLIC_METADATA_SERVICE_API_HOST', {
is: (value: string) => Boolean(value),
then: (schema) => schema,
otherwise: (schema) => schema.test(
'not-exist',
'NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED cannot not be used if NEXT_PUBLIC_METADATA_SERVICE_API_HOST is not defined',
value => value === undefined,
),
}),
});
const deFiDropdownItemSchema: yup.ObjectSchema<DeFiDropdownItem> = yup
.object({
text: yup.string().required(),
@@ -690,6 +700,46 @@ const externalTxsConfigSchema: yup.ObjectSchema<TxExternalTxsConfig> = yup.objec
explorer_url_template: yup.string().required(),
});
const address3rdPartyWidgetsConfigSchema = yup
.object()
.shape({
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL: yup
.mixed()
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL, it should have name, url, icon, title, value', (data) => {
const isUndefined = data === undefined;
const parsedData = typeof data === 'string' ? JSON.parse(data) : data;
const valueSchema = yup.lazy((objValue) => {
let schema = yup.object();
Object.keys(objValue).forEach((key) => {
schema = schema.shape({
[key]: yup.object<Address3rdPartyWidget>().shape({
name: yup.string().required(),
url: yup.string().required(),
icon: yup.string().required(),
title: yup.string().required(),
hint: yup.string().optional(),
valuePath: yup.string().required(),
pages: yup.array().of(yup.string().oneOf(ADDRESS_3RD_PARTY_WIDGET_PAGES)).required(),
chainIds: yup.object<Record<string, string>>().optional(),
}),
});
});
return schema;
});
return isUndefined || valueSchema.isValidSync(parsedData);
}),
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS: yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string())
.when('NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL', {
is: (value: string) => value,
then: (schema) => schema,
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS cannot not be used if NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL is not provided'),
}),
});
const schema = yup
.object()
.noUnknown(true, (params) => {
@@ -952,7 +1002,6 @@ const schema = yup
NEXT_PUBLIC_VISUALIZE_API_BASE_PATH: yup.string(),
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest),
NEXT_PUBLIC_NAME_SERVICE_API_HOST: yup.string().test(urlTest),
NEXT_PUBLIC_METADATA_SERVICE_API_HOST: yup.string().test(urlTest),
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup.string().test(urlTest),
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup
.mixed()
@@ -1107,6 +1156,8 @@ const schema = yup
.concat(beaconChainSchema)
.concat(bridgedTokensSchema)
.concat(sentrySchema)
.concat(tacSchema);
.concat(tacSchema)
.concat(address3rdPartyWidgetsConfigSchema)
.concat(addressMetadataSchema);
export default schema;
@@ -7,3 +7,5 @@ NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated
NEXT_PUBLIC_NETWORK_RPC_URL=['https://example.com','https://example2.com']
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://example.com
NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED=false
@@ -89,3 +89,5 @@ NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template
NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'dapp_id': 'smol-refuel', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&utm_medium=address&disableBridges=true', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'}
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true
NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://example.com
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['widget-1', 'widget-2']
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL=https://example.com
+1 -2
View File
@@ -1,2 +1 @@
NEXT_PUBLIC_CELO_ENABLED=true
NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK=420
NEXT_PUBLIC_CELO_ENABLED=true
@@ -0,0 +1,31 @@
{
"widget-1": {
"name": "Widget 1",
"url": "https://example.com/widget-1/{address}",
"icon": "https://example.com/icon.svg",
"title": "Widget 1",
"hint": "Widget 1 hint",
"valuePath": "result.value",
"pages": [ "eoa", "contract", "token" ]
},
"widget-2": {
"name": "Widget 2",
"url": "https://example.com/widget-2/{address}",
"icon": "https://example.com/icon.svg",
"title": "Widget 2",
"valuePath": "value",
"pages": [ "eoa" ]
},
"widget-3": {
"name": "Widget 3",
"url": "https://example.com/widget-3/{address}?chainId={chainId}",
"icon": "https://example.com/icon.svg",
"title": "Widget 3",
"valuePath": "result.length",
"pages": [ "token" ],
"chainIds": {
"1": "eth",
"10": "op"
}
}
}
@@ -17,7 +17,11 @@
"links": [
{
"text": "Develop",
"url": "https://example.com"
"url": "https://example.com",
"iconUrl": [
"https://example.com/mocks/image_s.jpg",
"https://example.com/mocks/image_svg.svg"
]
},
{
"text": "Grants",
@@ -0,0 +1,4 @@
node_modules
dist
*.log
.DS_Store
@@ -0,0 +1,80 @@
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, resolve as resolvePath } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Worker } from 'node:worker_threads';
const currentFilePath = fileURLToPath(import.meta.url);
const currentDir = dirname(currentFilePath);
const EXPLORER_URLS = [
'https://optimism-interop-alpha-0.blockscout.com',
'https://optimism-interop-alpha-1.blockscout.com',
];
function getSlug(url: string) {
return new URL(url).hostname.replace('.blockscout.com', '').replace('.k8s-dev', '');
}
async function computeChainConfig(url: string): Promise<unknown> {
return new Promise((resolve, reject) => {
const workerPath = resolvePath(currentDir, 'worker.js');
const worker = new Worker(workerPath, {
workerData: { url },
env: {} // Start with empty environment
});
worker.on('message', (config) => {
resolve(config);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
reject(error);
});
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${ code }`));
}
});
});
}
async function run() {
try {
if (!process.env.NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST) {
console.log('ℹ️ NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST is not set, skipping multichain config generation\n');
return;
}
console.log('🌀 Generating multichain config...');
const configs = await Promise.all(EXPLORER_URLS.map(computeChainConfig));
const config = {
chains: configs.map((config, index) => {
return {
slug: getSlug(EXPLORER_URLS[index]),
config,
};
}),
};
const outputDir = resolvePath(currentDir, '../../../../public/assets/multichain');
mkdirSync(outputDir, { recursive: true });
const outputPathJson = resolvePath(outputDir, 'config.json');
writeFileSync(outputPathJson, JSON.stringify(config, null, 2));
const outputPathJs = resolvePath(outputDir, 'config.js');
writeFileSync(outputPathJs, `window.__multichainConfig = ${ JSON.stringify(config) };`);
console.log('👍 Done!\n');
} catch (error) {
console.error('🚨 Error generating multichain config:', error);
console.log('\n');
process.exit(1);
}
}
run();
@@ -0,0 +1,18 @@
{
"name": "@blockscout/multichain-config-generator",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build --logLevel error",
"generate": "node dist/index.js"
},
"dependencies": {
},
"devDependencies": {
"typescript": "5.4.2",
"vite": "6.3.5",
"vite-plugin-dts": "4.5.4",
"@types/node": "22.12.0"
}
}
@@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"baseUrl": ".",
"paths": {
"configs/*": ["../../../configs/*"],
"lib/*": ["../../../lib/*"],
"toolkit/*": ["../../../toolkit/*"],
"types/*": ["../../../types/*"]
},
"types": ["node"],
"allowImportingTsExtensions": true,
"noEmit": true
},
"include": [
"index.ts",
"worker.ts",
"../app/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
@@ -0,0 +1,33 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: {
index: resolve(__dirname, 'index.ts'),
worker: resolve(__dirname, 'worker.ts'),
},
formats: [ 'es' ],
fileName: (format, entryName) => `${ entryName }.js`,
},
rollupOptions: {
external: [ 'node:worker_threads', 'node:url', 'node:path', 'node:fs' ],
output: {
dir: 'dist',
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: '[name].[ext]',
},
},
},
resolve: {
alias: {
configs: resolve(__dirname, '../../../configs'),
lib: resolve(__dirname, '../../../lib'),
toolkit: resolve(__dirname, '../../../toolkit'),
types: resolve(__dirname, '../../../types'),
},
preserveSymlinks: true,
},
});
@@ -0,0 +1,48 @@
/* eslint-disable no-console */
import { parentPort, workerData } from 'node:worker_threads';
interface WorkerData {
url: string;
}
interface ChainConfig {
envs: Record<string, string>;
}
async function fetchChainConfig(url: string): Promise<ChainConfig> {
const response = await fetch(`${ url }/node-api/config`);
if (!response.ok) {
throw new Error(`Failed to fetch config from ${ url }: ${ response.statusText }`);
}
const config = await response.json();
return config as ChainConfig;
}
async function computeConfig() {
try {
const { url } = workerData as WorkerData;
console.log(' ⏳ Fetching chain config from:', url);
// 1. Fetch chain config
const chainConfig = await fetchChainConfig(url);
// 2. Set environment variables
Object.entries(chainConfig.envs).forEach(([ key, value ]) => {
// eslint-disable-next-line no-restricted-properties
process.env[key] = value;
});
// 3. Import and compute app config
const { 'default': appConfig } = await import('configs/app/index');
console.log(' ✅ Config computed for:', url);
// 4. Send config back to main thread
parentPort?.postMessage(appConfig);
} catch (error) {
console.error(' ❌ Worker error:', error);
process.exit(1);
}
}
computeConfig();
@@ -0,0 +1,978 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/helper-string-parser@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-validator-identifier@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/parser@^7.27.2":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.5.tgz#ed22f871f110aa285a6fd934a0efed621d118826"
integrity sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/types@^7.27.3":
version "7.27.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.3.tgz#c0257bedf33aad6aad1f406d35c44758321eb3ec"
integrity sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz#4e0f91776c2b340e75558f60552195f6fad09f18"
integrity sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz#bc766407f1718923f6b8079c8c61bf86ac3a6a4f"
integrity sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz#4290d6d3407bae3883ad2cded1081a234473ce26"
integrity sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.5.tgz#40c11d9cbca4f2406548c8a9895d321bc3b35eff"
integrity sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz#49d8bf8b1df95f759ac81eb1d0736018006d7e34"
integrity sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz#e27a5d92a14886ef1d492fd50fc61a2d4d87e418"
integrity sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz#97cede59d638840ca104e605cdb9f1b118ba0b1c"
integrity sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz#71c77812042a1a8190c3d581e140d15b876b9c6f"
integrity sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz#f7b7c8f97eff8ffd2e47f6c67eb5c9765f2181b8"
integrity sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz#2a0be71b6cd8201fa559aea45598dffabc05d911"
integrity sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz#763414463cd9ea6fa1f96555d2762f9f84c61783"
integrity sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz#428cf2213ff786a502a52c96cf29d1fcf1eb8506"
integrity sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz#5cbcc7fd841b4cd53358afd33527cd394e325d96"
integrity sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz#0d954ab39ce4f5e50f00c4f8c4fd38f976c13ad9"
integrity sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz#0e7dd30730505abd8088321e8497e94b547bfb1e"
integrity sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz#5669af81327a398a336d7e40e320b5bbd6e6e72d"
integrity sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz#b2357dd153aa49038967ddc1ffd90c68a9d2a0d4"
integrity sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz#53b4dfb8fe1cee93777c9e366893bd3daa6ba63d"
integrity sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz#a0206f6314ce7dc8713b7732703d0f58de1d1e79"
integrity sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz#2a796c87c44e8de78001d808c77d948a21ec22fd"
integrity sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz#28d0cd8909b7fa3953af998f2b2ed34f576728f0"
integrity sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz#a28164f5b997e8247d407e36c90d3fd5ddbe0dc5"
integrity sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz#6eadbead38e8bd12f633a5190e45eff80e24007e"
integrity sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz#bab6288005482f9ed2adb9ded7e88eba9a62cc0d"
integrity sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==
"@esbuild/[email protected]":
version "0.25.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz#7fc114af5f6563f19f73324b5d5ff36ece0803d1"
integrity sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==
"@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@microsoft/[email protected]":
version "7.30.6"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.30.6.tgz#cd9c434521dda3b226cc0f6aefb9c20afaf99c92"
integrity sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==
dependencies:
"@microsoft/tsdoc" "~0.15.1"
"@microsoft/tsdoc-config" "~0.17.1"
"@rushstack/node-core-library" "5.13.1"
"@microsoft/api-extractor@^7.50.1":
version "7.52.8"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.52.8.tgz#7cc944f44ca1b1ad9d7272ab5d98e81987c1f8ca"
integrity sha512-cszYIcjiNscDoMB1CIKZ3My61+JOhpERGlGr54i6bocvGLrcL/wo9o+RNXMBrb7XgLtKaizZWUpqRduQuHQLdg==
dependencies:
"@microsoft/api-extractor-model" "7.30.6"
"@microsoft/tsdoc" "~0.15.1"
"@microsoft/tsdoc-config" "~0.17.1"
"@rushstack/node-core-library" "5.13.1"
"@rushstack/rig-package" "0.5.3"
"@rushstack/terminal" "0.15.3"
"@rushstack/ts-command-line" "5.0.1"
lodash "~4.17.15"
minimatch "~3.0.3"
resolve "~1.22.1"
semver "~7.5.4"
source-map "~0.6.1"
typescript "5.8.2"
"@microsoft/tsdoc-config@~0.17.1":
version "0.17.1"
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz#e0f0b50628f4ad7fe121ca616beacfe6a25b9335"
integrity sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==
dependencies:
"@microsoft/tsdoc" "0.15.1"
ajv "~8.12.0"
jju "~1.4.0"
resolve "~1.22.2"
"@microsoft/[email protected]", "@microsoft/tsdoc@~0.15.1":
version "0.15.1"
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2"
integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==
"@rollup/pluginutils@^5.1.4":
version "5.1.4"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a"
integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==
dependencies:
"@types/estree" "^1.0.0"
estree-walker "^2.0.2"
picomatch "^4.0.2"
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz#f39f09f60d4a562de727c960d7b202a2cf797424"
integrity sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz#d19af7e23760717f1d879d4ca3d2cd247742dff2"
integrity sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz#1c3a2fbf205d80641728e05f4a56c909e95218b7"
integrity sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz#aa66d2ba1a25e609500e13bef06dc0e71cc0c0d4"
integrity sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz#df10a7b6316a0ef1028c6ca71a081124c537e30d"
integrity sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz#a3fdce8a05e95b068cbcb46e4df5185e407d0c35"
integrity sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz#49f766c55383bd0498014a9d76924348c2f3890c"
integrity sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz#1d4d7d32fc557e17d52e1857817381ea365e2959"
integrity sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz#f4fc317268441e9589edad3be8f62b6c03009bc1"
integrity sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz#63a1f1b0671cb17822dabae827fef0e443aebeb7"
integrity sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz#c659b01cc6c0730b547571fc3973e1e955369f98"
integrity sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz#612e746f9ad7e58480f964d65e0d6c3f4aae69a8"
integrity sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz#4610dbd1dcfbbae32fbc10c20ae7387acb31110c"
integrity sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz#054911fab40dc83fafc21e470193c058108f19d8"
integrity sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz#98896eca8012547c7f04bd07eaa6896825f9e1a5"
integrity sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz#01cf56844a1e636ee80dfb364e72c2b7142ad896"
integrity sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz#e67c7531df6dff0b4c241101d4096617fbca87c3"
integrity sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz#7eeada98444e580674de6989284e4baacd48ea65"
integrity sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz#516c4b54f80587b4a390aaf4940b40870271d35d"
integrity sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==
"@rollup/[email protected]":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz#848f99b0d9936d92221bb6070baeff4db6947a30"
integrity sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==
"@rushstack/[email protected]":
version "5.13.1"
resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-5.13.1.tgz#e56b915ecb08b5a92711acac6b233417353a32dc"
integrity sha512-5yXhzPFGEkVc9Fu92wsNJ9jlvdwz4RNb2bMso+/+TH0nMm1jDDDsOIf4l8GAkPxGuwPw5DH24RliWVfSPhlW/Q==
dependencies:
ajv "~8.13.0"
ajv-draft-04 "~1.0.0"
ajv-formats "~3.0.1"
fs-extra "~11.3.0"
import-lazy "~4.0.0"
jju "~1.4.0"
resolve "~1.22.1"
semver "~7.5.4"
"@rushstack/[email protected]":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.5.3.tgz#ea4d8a3458540b1295500149c04e645f23134e5d"
integrity sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==
dependencies:
resolve "~1.22.1"
strip-json-comments "~3.1.1"
"@rushstack/[email protected]":
version "0.15.3"
resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.15.3.tgz#365e0ae5ac73bb4883b096ae36c5011f52911861"
integrity sha512-DGJ0B2Vm69468kZCJkPj3AH5nN+nR9SPmC0rFHtzsS4lBQ7/dgOwtwVxYP7W9JPDMuRBkJ4KHmWKr036eJsj9g==
dependencies:
"@rushstack/node-core-library" "5.13.1"
supports-color "~8.1.1"
"@rushstack/[email protected]":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-5.0.1.tgz#e147394b5ce87ef79db95b5b4f155461d6f2c50e"
integrity sha512-bsbUucn41UXrQK7wgM8CNM/jagBytEyJqXw/umtI8d68vFm1Jwxh1OtLrlW7uGZgjCWiiPH6ooUNa1aVsuVr3Q==
dependencies:
"@rushstack/terminal" "0.15.3"
"@types/argparse" "1.0.38"
argparse "~1.0.9"
string-argv "~0.3.1"
"@types/[email protected]":
version "1.0.38"
resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9"
integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==
"@types/[email protected]", "@types/estree@^1.0.0":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
"@types/[email protected]":
version "22.12.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c"
integrity sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==
dependencies:
undici-types "~6.20.0"
"@volar/[email protected]", "@volar/language-core@~2.4.11":
version "2.4.14"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.14.tgz#dac7573014d4f3bafb186cb16888ffea5698be71"
integrity sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==
dependencies:
"@volar/source-map" "2.4.14"
"@volar/[email protected]":
version "2.4.14"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.14.tgz#cdcecd533c2e767449b2414cc22327d2bda7ef95"
integrity sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ==
"@volar/typescript@^2.4.11":
version "2.4.14"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.14.tgz#b99a1025dd6a8b751e96627ebcb0739ceed0e5f1"
integrity sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw==
dependencies:
"@volar/language-core" "2.4.14"
path-browserify "^1.0.1"
vscode-uri "^3.0.8"
"@vue/[email protected]":
version "3.5.16"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.16.tgz#2f95f4f17c16c09c57bbf64399075b921506630b"
integrity sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==
dependencies:
"@babel/parser" "^7.27.2"
"@vue/shared" "3.5.16"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.1"
"@vue/compiler-dom@^3.5.0":
version "3.5.16"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz#151d8390252975c0b1a773029220fdfcfaa2d743"
integrity sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==
dependencies:
"@vue/compiler-core" "3.5.16"
"@vue/shared" "3.5.16"
"@vue/compiler-vue2@^2.7.16":
version "2.7.16"
resolved "https://registry.yarnpkg.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz#2ba837cbd3f1b33c2bc865fbe1a3b53fb611e249"
integrity sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==
dependencies:
de-indent "^1.0.2"
he "^1.2.0"
"@vue/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.0.tgz#e48c54584f889f78b120ce10a050dfb316c7fcdf"
integrity sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==
dependencies:
"@volar/language-core" "~2.4.11"
"@vue/compiler-dom" "^3.5.0"
"@vue/compiler-vue2" "^2.7.16"
"@vue/shared" "^3.5.0"
alien-signals "^0.4.9"
minimatch "^9.0.3"
muggle-string "^0.4.1"
path-browserify "^1.0.1"
"@vue/[email protected]", "@vue/shared@^3.5.0":
version "3.5.16"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.16.tgz#d5ea7671182742192938a4b4cbf86ef12bef7418"
integrity sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==
acorn@^8.14.0:
version "8.14.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
ajv-draft-04@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8"
integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==
ajv-formats@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578"
integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==
dependencies:
ajv "^8.0.0"
ajv@^8.0.0:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
dependencies:
fast-deep-equal "^3.1.3"
fast-uri "^3.0.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
ajv@~8.12.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ajv@~8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91"
integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==
dependencies:
fast-deep-equal "^3.1.3"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.4.1"
alien-signals@^0.4.9:
version "0.4.14"
resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-0.4.14.tgz#9ff8f72a272300a51692f54bd9bbbada78fbf539"
integrity sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==
argparse@~1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
compare-versions@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.1.tgz#7af3cc1099ba37d244b3145a9af5201b629148a9"
integrity sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==
[email protected]:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
confbox@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
confbox@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110"
integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
debug@^4.4.0:
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies:
ms "^2.1.3"
entities@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild@^0.25.0:
version "0.25.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.5.tgz#71075054993fdfae76c66586f9b9c1f8d7edd430"
integrity sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==
optionalDependencies:
"@esbuild/aix-ppc64" "0.25.5"
"@esbuild/android-arm" "0.25.5"
"@esbuild/android-arm64" "0.25.5"
"@esbuild/android-x64" "0.25.5"
"@esbuild/darwin-arm64" "0.25.5"
"@esbuild/darwin-x64" "0.25.5"
"@esbuild/freebsd-arm64" "0.25.5"
"@esbuild/freebsd-x64" "0.25.5"
"@esbuild/linux-arm" "0.25.5"
"@esbuild/linux-arm64" "0.25.5"
"@esbuild/linux-ia32" "0.25.5"
"@esbuild/linux-loong64" "0.25.5"
"@esbuild/linux-mips64el" "0.25.5"
"@esbuild/linux-ppc64" "0.25.5"
"@esbuild/linux-riscv64" "0.25.5"
"@esbuild/linux-s390x" "0.25.5"
"@esbuild/linux-x64" "0.25.5"
"@esbuild/netbsd-arm64" "0.25.5"
"@esbuild/netbsd-x64" "0.25.5"
"@esbuild/openbsd-arm64" "0.25.5"
"@esbuild/openbsd-x64" "0.25.5"
"@esbuild/sunos-x64" "0.25.5"
"@esbuild/win32-arm64" "0.25.5"
"@esbuild/win32-ia32" "0.25.5"
"@esbuild/win32-x64" "0.25.5"
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
exsolve@^1.0.1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.5.tgz#1f5b6b4fe82ad6b28a173ccb955a635d77859dcf"
integrity sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-uri@^3.0.1:
version "3.0.6"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748"
integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==
fdir@^6.4.4:
version "6.4.5"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.5.tgz#328e280f3a23699362f95f2e82acf978a0c0cb49"
integrity sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==
fs-extra@~11.3.0:
version "11.3.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
import-lazy@~4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
is-core-module@^2.16.0:
version "2.16.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
dependencies:
hasown "^2.0.2"
jju@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
kolorist@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
local-pkg@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.1.tgz#f5fe74a97a3bd3c165788ee08ca9fbe998dc58dd"
integrity sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==
dependencies:
mlly "^1.7.4"
pkg-types "^2.0.1"
quansync "^0.2.8"
lodash@~4.17.15:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
magic-string@^0.30.17:
version "0.30.17"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
minimatch@^9.0.3:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
dependencies:
brace-expansion "^2.0.1"
minimatch@~3.0.3:
version "3.0.8"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==
dependencies:
brace-expansion "^1.1.7"
mlly@^1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.4.tgz#3d7295ea2358ec7a271eaa5d000a0f84febe100f"
integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==
dependencies:
acorn "^8.14.0"
pathe "^2.0.1"
pkg-types "^1.3.0"
ufo "^1.5.4"
ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
muggle-string@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328"
integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==
nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
pathe@^2.0.1, pathe@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
pkg-types@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df"
integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==
dependencies:
confbox "^0.1.8"
mlly "^1.7.4"
pathe "^2.0.1"
pkg-types@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.1.0.tgz#70c9e1b9c74b63fdde749876ee0aa007ea9edead"
integrity sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==
dependencies:
confbox "^0.2.1"
exsolve "^1.0.1"
pathe "^2.0.3"
postcss@^8.5.3:
version "8.5.4"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.4.tgz#d61014ac00e11d5f58458ed7247d899bd65f99c0"
integrity sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
quansync@^0.2.8:
version "0.2.10"
resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.10.tgz#32053cf166fa36511aae95fc49796116f2dc20e1"
integrity sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
resolve@~1.22.1, resolve@~1.22.2:
version "1.22.10"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
dependencies:
is-core-module "^2.16.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
rollup@^4.34.9:
version "4.41.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.41.1.tgz#46ddc1b33cf1b0baa99320d3b0b4973dc2253b6a"
integrity sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==
dependencies:
"@types/estree" "1.0.7"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.41.1"
"@rollup/rollup-android-arm64" "4.41.1"
"@rollup/rollup-darwin-arm64" "4.41.1"
"@rollup/rollup-darwin-x64" "4.41.1"
"@rollup/rollup-freebsd-arm64" "4.41.1"
"@rollup/rollup-freebsd-x64" "4.41.1"
"@rollup/rollup-linux-arm-gnueabihf" "4.41.1"
"@rollup/rollup-linux-arm-musleabihf" "4.41.1"
"@rollup/rollup-linux-arm64-gnu" "4.41.1"
"@rollup/rollup-linux-arm64-musl" "4.41.1"
"@rollup/rollup-linux-loongarch64-gnu" "4.41.1"
"@rollup/rollup-linux-powerpc64le-gnu" "4.41.1"
"@rollup/rollup-linux-riscv64-gnu" "4.41.1"
"@rollup/rollup-linux-riscv64-musl" "4.41.1"
"@rollup/rollup-linux-s390x-gnu" "4.41.1"
"@rollup/rollup-linux-x64-gnu" "4.41.1"
"@rollup/rollup-linux-x64-musl" "4.41.1"
"@rollup/rollup-win32-arm64-msvc" "4.41.1"
"@rollup/rollup-win32-ia32-msvc" "4.41.1"
"@rollup/rollup-win32-x64-msvc" "4.41.1"
fsevents "~2.3.2"
semver@~7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
string-argv@~0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
strip-json-comments@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
supports-color@~8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tinyglobby@^0.2.13:
version "0.2.14"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==
dependencies:
fdir "^6.4.4"
picomatch "^4.0.2"
[email protected]:
version "5.4.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372"
integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==
[email protected]:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
ufo@^1.5.4:
version "1.6.1"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b"
integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==
undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
uri-js@^4.2.2, uri-js@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
[email protected]:
version "4.5.4"
resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz#51b60aaaa760d9cf5c2bb3676c69d81910d6b08c"
integrity sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==
dependencies:
"@microsoft/api-extractor" "^7.50.1"
"@rollup/pluginutils" "^5.1.4"
"@volar/typescript" "^2.4.11"
"@vue/language-core" "2.2.0"
compare-versions "^6.1.1"
debug "^4.4.0"
kolorist "^1.8.0"
local-pkg "^1.0.0"
magic-string "^0.30.17"
[email protected]:
version "6.3.5"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.3.5.tgz#fec73879013c9c0128c8d284504c6d19410d12a3"
integrity sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==
dependencies:
esbuild "^0.25.0"
fdir "^6.4.4"
picomatch "^4.0.2"
postcss "^8.5.3"
rollup "^4.34.9"
tinyglobby "^0.2.13"
optionalDependencies:
fsevents "~2.3.3"
vscode-uri@^3.0.8:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c"
integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+1 -25
View File
@@ -48,32 +48,8 @@ frontend:
cpu: 250m
env:
NEXT_PUBLIC_APP_ENV: review
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json
NEXT_PUBLIC_API_HOST: base.blockscout.com
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST: https://stats-l2-base-mainnet.k8s-prod-1.blockscout.com
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)"
NEXT_PUBLIC_NETWORK_RPC_URL: https://mainnet.base.org
NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']"
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: true
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs']"
NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs.services.blockscout.com
NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens.services.blockscout.com
NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata.services.blockscout.com
NEXT_PUBLIC_ROLLUP_TYPE: optimistic
NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth.blockscout.com
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_NAVIGATION_LAYOUT: horizontal
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/blocks','/name-domains']"
SKIP_ENVS_VALIDATION: true
envFromSecret:
NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
NEXT_PUBLIC_OG_IMAGE_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/base-mainnet.png
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/eth-sepolia/testnet?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_CLIENT_KEY
+1
View File
@@ -12,3 +12,4 @@
| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.12.0 | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL |
| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.24.0 | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS |
| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | v1.0.x+ | v1.35.0 | Replaced by NEXT_PUBLIC_HOMEPAGE_STATS |
| NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK | `number` | Indicates the block number when the Celo-type chain transitioned to L2. This is used to display links to the Epoch block page from a regular block page. | - | - | `26369280` | v1.37.0+ | v2.2.0 | Removed; configuration done on the API side |
+27 -2
View File
@@ -76,6 +76,7 @@ All json-like values should be single-quoted. If it contains a hash (`#`) or a d
- [Save on gas with GasHawk](#save-on-gas-with-gashawk)
- [Rewards service API](#rewards-service-api)
- [DEX pools](#dex-pools)
- [Address 3rd party widgets](#address-3rd-party-widgets)
- [3rd party services configuration](#external-services-configuration)
&nbsp;
@@ -192,7 +193,7 @@ The app version shown in the footer is derived from build-time ENV variables `NE
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| title | `string` | Title of link group | Required | - | `Company` |
| links | `Array<{'text':string;'url':string;}>` | list of links | Required | - | `[{'text':'Homepage','url':'https://www.blockscout.com'}]` |
| links | `Array<{'text':string;'url':string;'iconUrl'?:[string,string]}>` | An array contains a list of links in the column. Each link can optionally have an `icon_url` property, which should include an array of two external image URLs for light and dark themes, respectively. If only one URL is provided, it will be used for both color schemes. We expect the icons to be square, with a minimum size of 40px by 40px or in SVG format. | Required | - | `[{'text':'Homepage','url':'https://www.blockscout.com'}]` |
&nbsp;
@@ -657,6 +658,7 @@ This feature allows name tags and other public tags for addresses.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_METADATA_SERVICE_API_HOST | `string` | Metadata Service API endpoint url | Required | - | `https://metadata.services.blockscout.com` | v1.30.0+ |
| NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED | `boolean` | Enables requests to the Metadata Service to schedule an update for address tags after the user visits the address page in the app. | - | `true` | `false` | v2.2.0+ |
&nbsp;
@@ -771,7 +773,6 @@ For blockchains that use the Celo platform. _Note_, that once the Celo mainnet b
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_CELO_ENABLED | `boolean` | Indicates that it is a Celo-based chain. | - | - | `true` | v1.37.0+ |
| NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK | `number` | Indicates the block number when the Celo-type chain transitioned to L2. This is used to display links to the Epoch block page from a regular block page. | - | - | `26369280` | v1.37.0+ |
&nbsp;
@@ -917,6 +918,30 @@ This feature enables Blockscout Merits program. It requires that the [My account
&nbsp;
### Address 3rd party widgets
This feature allows to display widgets on the address page with data from 3rd party services.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS | `Array<string>` | Array of widget ids to be displayed | - | - | `['widget-1', 'widget-2']` | v2.2.0+ |
| NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains mapping of widget names to their configuration. See [below](#address-3rd-party-widget-configuration-properties) list of available properties for a widget. | - | - | `https://example.com/address_3rd_party_widgets_config.json` | v2.2.0+ |
#### Address 3rd party widget configuration properties
| Property | Type | Description | Compulsoriness | Example value |
| --- | --- | --- | --- | --- |
| name | `string` | Displayed name of the widget | Required | - | `'Widget'` |
| url | `string` | Link URL for widget card. Can contain `{address}`, `{addressLowercase}` and `{chainId}` variables | Required | - | `'https://example.com/widget/{address}?chainId={chainId}'` |
| icon | `string` | Widget icon URL | Required | - | `'https://example.com/icon.svg'` |
| title | `string` | Title of displayed data | Required | - | `'Multichain balance'` |
| hint | `string` | Hint for displayed data | - | - | `'Widget hint'` |
| valuePath | `string` | Path to the field in the API response that contains the value to be displayed | Required | - | `'result.balance'` |
| pages | `Array<'eoa' \| 'contract' \| 'token'>` | List of pages where the widget should be displayed | Required | - | `['eoa']` |
| chainIds | `Record<string, string>` | Mapping of chain IDs to custom values that will be used in `url` template | - | - | `{'1': 'eth', '10': 'op'}` |
&nbsp;
### Badge claim link
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
Vendored
+2
View File
@@ -1,3 +1,4 @@
import type { MultichainConfig } from 'types/multichain';
import type { WalletProvider } from 'types/web3';
type CPreferences = {
@@ -19,6 +20,7 @@ declare global {
};
abkw: string;
__envs: Record<string, string>;
__multichainConfig: MultichainConfig;
}
namespace NodeJS {
+1 -1
View File
@@ -1,3 +1,3 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.164.859a9.3 9.3 0 0 1 8.3 2.457l.047.046c.136.134.268.272.395.415l.03.033c.207.234.403.478.587.733l.039.055.15.214c.087.131.17.266.25.401.15.252.29.51.415.773.012.024.022.049.033.073.084.18.161.362.233.546l.03.08a9.3 9.3 0 0 1 .607 3.297l-.012.46a9.301 9.301 0 0 1-2.395 5.781l-.317.334a9.3 9.3 0 0 1-6.114 2.712l-.461.012A9.3 9.3 0 0 1 5.105 17.9l-.29-.187a9.303 9.303 0 0 1-.819-.615l-.055-.049a9.33 9.33 0 0 1-.438-.397l-.098-.098a8.814 8.814 0 0 1-.351-.371l-.078-.088a9.32 9.32 0 0 1-.16-.188l-.062-.075a9.397 9.397 0 0 1-.294-.383A9.297 9.297 0 0 1 8.164.859Zm1.817 1.509a7.617 7.617 0 0 0-3.994 1.13l-.237.153A7.616 7.616 0 0 0 3.06 6.81l-.113.259a7.615 7.615 0 0 0-.433 4.398l.06.277a7.613 7.613 0 0 0 2.024 3.622l.203.196a7.615 7.615 0 0 0 3.695 1.888l.278.05a7.614 7.614 0 0 0 4.12-.484l.26-.112a7.615 7.615 0 0 0 3.159-2.692l.152-.237a7.616 7.616 0 0 0 1.13-3.993l-.008-.378a7.614 7.614 0 0 0-1.962-4.732l-.26-.274a7.614 7.614 0 0 0-5.005-2.22l-.378-.01Zm.083 1.138c.192.02.374.104.512.242l.056.062c.123.15.191.339.191.534v5.29l3.631 3.626c.079.078.142.17.185.274l.027.078c.024.08.037.162.037.245l-.005.084a.84.84 0 0 1-.032.162l-.027.078a.852.852 0 0 1-.129.213l-.057.062a.837.837 0 0 1-.199.146l-.075.034a.841.841 0 0 1-.566.029l-.079-.029a.842.842 0 0 1-.274-.18l-3.877-3.877a.853.853 0 0 1-.146-.2l-.036-.075a.846.846 0 0 1-.063-.323V4.344c0-.223.09-.438.247-.596l.061-.055a.844.844 0 0 1 .535-.192l.083.005Z" fill="currentColor"/>
<path d="M8.164.859a9.3 9.3 0 0 1 8.3 2.457l.047.046c.136.134.268.272.395.415l.03.033c.207.234.403.478.587.733l.039.055.15.214c.087.131.17.266.25.401.15.252.29.51.415.773.012.024.022.049.033.073.084.18.161.362.233.546l.03.08a9.3 9.3 0 0 1 .607 3.297l-.012.46a9.301 9.301 0 0 1-2.395 5.781l-.317.334a9.3 9.3 0 0 1-6.114 2.712l-.461.012A9.3 9.3 0 0 1 5.105 17.9l-.29-.187a9.303 9.303 0 0 1-.819-.615l-.055-.049a9.33 9.33 0 0 1-.438-.397l-.098-.098a8.814 8.814 0 0 1-.351-.371l-.078-.088a9.32 9.32 0 0 1-.16-.188l-.062-.075a9.397 9.397 0 0 1-.294-.383A9.297 9.297 0 0 1 8.164.859Zm1.817 1.509a7.617 7.617 0 0 0-3.994 1.13l-.237.153A7.616 7.616 0 0 0 3.06 6.81l-.113.259a7.615 7.615 0 0 0-.433 4.398l.06.277a7.613 7.613 0 0 0 2.024 3.622l.203.196a7.615 7.615 0 0 0 3.695 1.888l.278.05a7.614 7.614 0 0 0 4.12-.484l.26-.112a7.615 7.615 0 0 0 3.159-2.692l.152-.237a7.616 7.616 0 0 0 1.13-3.993l-.008-.378a7.614 7.614 0 0 0-1.962-4.732l-.26-.274a7.614 7.614 0 0 0-5.005-2.22l-.378-.01Zm.083 1.138c.192.02.374.104.512.242l.056.062c.123.15.191.339.191.534v5.29l3.631 3.626a.83.83 0 0 1 .185.274l.027.078a.85.85 0 0 1 .037.245l-.005.084a.84.84 0 0 1-.032.162l-.027.078a.852.852 0 0 1-.129.213l-.057.062a.837.837 0 0 1-.199.146l-.075.034a.841.841 0 0 1-.566.029l-.079-.029a.842.842 0 0 1-.274-.18l-3.877-3.877a.853.853 0 0 1-.146-.2l-.036-.075a.846.846 0 0 1-.063-.323V4.344c0-.223.09-.438.247-.596l.061-.055a.844.844 0 0 1 .535-.192l.083.005Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

+3 -2
View File
@@ -1,3 +1,4 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.125 6.906a1.257 1.257 0 0 0 .5-1V3.125a1.25 1.25 0 0 0-1.25-1.25h-8.75a1.25 1.25 0 0 0-1.25 1.25v2.813a1.258 1.258 0 0 0 .5 1L6.289 8l2.672 2-4.086 3.063a1.258 1.258 0 0 0-.5 1v2.812a1.25 1.25 0 0 0 1.25 1.25h8.75a1.25 1.25 0 0 0 1.25-1.25v-2.781a1.257 1.257 0 0 0-.5-1L11.039 10l1.774-1.344 2.312-1.75Zm-9.5-3.781h8.75v2.781l-.453.344H6.039l-.414-.313V3.126Zm8.75 10.969v2.781h-8.75v-2.813l3.75-2.812v1.875a.625.625 0 1 0 1.25 0v-1.867l3.75 2.836Z" fill="currentColor"/>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.052 4.708c.415.037.82.204 1.141.493l.132.132c.293.323.47.746.47 1.206v3.43a1.785 1.785 0 0 1-.128.654l-.096.201a1.875 1.875 0 0 1-.58.635l-.003.002L16.796 15l5.192 3.54.014.01.145.11.013.012c.163.14.304.31.411.504l.096.201c.083.206.127.428.129.654v3.43l-.01.195a1.8 1.8 0 0 1-.46 1.012l-.133.132c-.32.29-.726.455-1.141.492l-.179.008H8.923c-.417 0-.833-.13-1.177-.384l-.142-.116a1.821 1.821 0 0 1-.567-.978l-.026-.166-.002-.016L7 23.478v-3.483l.001-.021.014-.188.002-.018.03-.166c.038-.162.098-.319.179-.464l.01-.016.098-.155.01-.015.101-.128c.104-.12.223-.225.352-.314l.016-.012L12.986 15l-5.174-3.498-.015-.012a1.889 1.889 0 0 1-.453-.442l-.118-.186a1.799 1.799 0 0 1-.21-.63L7 10.008V6.539a1.8 1.8 0 0 1 .604-1.338l.142-.116c.343-.254.76-.385 1.177-.385h11.95l.179.008ZM9.139 20.182v2.98h11.52v-2.941l-4.691-3.198v1.823a1.07 1.07 0 0 1-.96 1.063l-.11.006a1.07 1.07 0 0 1-1.069-1.07v-1.832l-4.69 3.169Zm-.121.082h.002l.006-.006-.008.006Zm5.877-6.557 2.176-1.483H12.7l2.196 1.483Zm-5.756-3.89.395.268H20.21l.45-.307v-2.94H9.138v2.98Z" fill="currentColor"/>
<path d="M21.052 4.708c.415.037.82.204 1.141.493l.132.132c.293.323.47.746.47 1.206v3.43a1.785 1.785 0 0 1-.128.654l-.096.201a1.875 1.875 0 0 1-.58.635l-.003.002L16.796 15l5.192 3.54.014.01.145.11.013.012c.163.14.304.31.411.504l.096.201c.083.206.127.428.129.654v3.43l-.01.195a1.8 1.8 0 0 1-.46 1.012l-.133.132c-.32.29-.726.455-1.141.492l-.179.008H8.923c-.417 0-.833-.13-1.177-.384l-.142-.116a1.821 1.821 0 0 1-.567-.978l-.026-.166-.002-.016L7 23.478v-3.483l.001-.021.014-.188.002-.018.03-.166c.038-.162.098-.319.179-.464l.01-.016.098-.155.01-.015.101-.128c.104-.12.223-.225.352-.314l.016-.012L12.986 15l-5.174-3.498-.015-.012a1.889 1.889 0 0 1-.453-.442l-.118-.186a1.799 1.799 0 0 1-.21-.63L7 10.008V6.539a1.8 1.8 0 0 1 .604-1.338l.142-.116c.343-.254.76-.385 1.177-.385h11.95l.179.008ZM9.139 20.182v2.98h11.52v-2.941l-4.691-3.198v1.823a1.07 1.07 0 0 1-.96 1.063l-.11.006a1.07 1.07 0 0 1-1.069-1.07v-1.832l-4.69 3.169Zm-.121.082h.002l.006-.006-.008.006Zm5.877-6.557 2.176-1.483H12.7l2.196 1.483Zm-5.756-3.89.395.268H20.21l.45-.307v-2.94H9.138v2.98Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 567 B

After

Width:  |  Height:  |  Size: 2.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.125 6.906a1.257 1.257 0 0 0 .5-1V3.125a1.25 1.25 0 0 0-1.25-1.25h-8.75a1.25 1.25 0 0 0-1.25 1.25v2.813a1.258 1.258 0 0 0 .5 1L6.289 8l2.672 2-4.086 3.063a1.258 1.258 0 0 0-.5 1v2.812a1.25 1.25 0 0 0 1.25 1.25h8.75a1.25 1.25 0 0 0 1.25-1.25v-2.781a1.257 1.257 0 0 0-.5-1L11.039 10l1.774-1.344 2.312-1.75Zm-9.5-3.781h8.75v2.781l-.453.344H6.039l-.414-.313V3.126Zm8.75 10.969v2.781h-8.75v-2.813l3.75-2.812v1.875a.625.625 0 1 0 1.25 0v-1.867l3.75 2.836Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 567 B

+5 -2
View File
@@ -1,5 +1,8 @@
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs' && process.env.NEXT_OPEN_TELEMETRY_ENABLED === 'true') {
await import('./instrumentation.node');
if (process.env.NEXT_RUNTIME === 'nodejs') {
if (process.env.NEXT_OPEN_TELEMETRY_ENABLED === 'true') {
await import('./instrumentation.node');
}
await import('./startup.node');
}
}
+7 -2
View File
@@ -2,6 +2,7 @@ import type { AddressMetadataInfoFormatted, AddressMetadataTagFormatted } from '
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { useMultichainContext } from 'lib/contexts/multichain';
import parseMetaPayload from './parseMetaPayload';
@@ -9,14 +10,18 @@ export default function useAddressMetadataInfoQuery(addresses: Array<string>, is
const resource = 'metadata:info';
const multichainContext = useMultichainContext();
const feature = multichainContext?.chain?.config.features.addressMetadata || config.features.addressMetadata;
const chainId = multichainContext?.chain?.config.chain.id || config.chain.id;
return useApiQuery<typeof resource, unknown, AddressMetadataInfoFormatted>(resource, {
queryParams: {
addresses,
chainId: config.chain.id,
chainId,
tagsLimit: '20',
},
queryOptions: {
enabled: isEnabled && addresses.length > 0 && config.features.addressMetadata.isEnabled,
enabled: isEnabled && addresses.length > 0 && feature.isEnabled && Boolean(chainId),
select: (data) => {
const addresses = Object.entries(data.addresses)
.map(([ address, { tags, reputation } ]) => {
@@ -0,0 +1,40 @@
import React from 'react';
import type { AddressCounters } from 'types/api/address';
import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch';
const feature = config.features.addressMetadata;
interface Params {
address: string | undefined;
counters: AddressCounters | undefined;
isEnabled: boolean;
}
const TXS_THRESHOLD = 500;
export default function useAddressMetadataInitUpdate({ address, counters, isEnabled }: Params) {
const apiFetch = useApiFetch();
React.useEffect(() => {
if (
feature.isEnabled &&
feature.isAddressTagsUpdateEnabled &&
address &&
isEnabled &&
counters?.transactions_count && Number(counters.transactions_count) > TXS_THRESHOLD
) {
apiFetch('metadata:address_submit', {
fetchParams: {
method: 'POST',
body: {
addresses: [ address ],
},
},
});
}
}, [ address, apiFetch, counters?.transactions_count, isEnabled ]);
}
+4 -1
View File
@@ -1,5 +1,7 @@
import { compile } from 'path-to-regexp';
import type { ChainConfig } from 'types/multichain';
import config from 'configs/app';
import getResourceParams from './getResourceParams';
@@ -11,8 +13,9 @@ export default function buildUrl<R extends ResourceName>(
pathParams?: ResourcePathParams<R>,
queryParams?: Record<string, string | Array<string> | number | boolean | null | undefined>,
noProxy?: boolean,
chain?: ChainConfig,
): string {
const { api, resource } = getResourceParams(resourceFullName);
const { api, resource } = getResourceParams(resourceFullName, chain);
const baseUrl = !noProxy && isNeedProxy() ? config.app.baseUrl : api.endpoint;
const basePath = api.basePath ?? '';
const path = !noProxy && isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
+10 -2
View File
@@ -1,13 +1,21 @@
import type { ApiName, ApiResource } from './types';
import type { ChainConfig } from 'types/multichain';
import config from 'configs/app';
import type { ResourceName } from './resources';
import { RESOURCES } from './resources';
export default function getResourceParams(resourceFullName: ResourceName) {
export default function getResourceParams(resourceFullName: ResourceName, chain?: ChainConfig) {
const [ apiName, resourceName ] = resourceFullName.split(':') as [ ApiName, string ];
const apiConfig = config.apis[apiName];
const apiConfig = (() => {
if (chain) {
return chain.config.apis[apiName];
}
return config.apis[apiName];
})();
if (!apiConfig) {
throw new Error(`API config for ${ apiName } not found`);
+5
View File
@@ -0,0 +1,5 @@
import appConfig from 'configs/app';
export default function getSocketUrl(config: typeof appConfig = appConfig) {
return `${ config.apis.general.socketEndpoint }${ config.apis.general.basePath ?? '' }/socket/v2`;
}
+4
View File
@@ -10,6 +10,8 @@ import { GENERAL_API_RESOURCES } from './services/general';
import type { GeneralApiResourceName, GeneralApiResourcePayload, GeneralApiPaginationFilters, GeneralApiPaginationSorting } from './services/general';
import type { MetadataApiResourceName, MetadataApiResourcePayload } from './services/metadata';
import { METADATA_API_RESOURCES } from './services/metadata';
import type { MultichainApiResourceName, MultichainApiResourcePayload } from './services/multichain';
import { MULTICHAIN_API_RESOURCES } from './services/multichain';
import type { RewardsApiResourceName, RewardsApiResourcePayload } from './services/rewards';
import { REWARDS_API_RESOURCES } from './services/rewards';
import type { StatsApiResourceName, StatsApiResourcePayload } from './services/stats';
@@ -30,6 +32,7 @@ export const RESOURCES = {
contractInfo: CONTRACT_INFO_API_RESOURCES,
general: GENERAL_API_RESOURCES,
metadata: METADATA_API_RESOURCES,
multichain: MULTICHAIN_API_RESOURCES,
rewards: REWARDS_API_RESOURCES,
stats: STATS_API_RESOURCES,
tac: TAC_OPERATION_LIFECYCLE_API_RESOURCES,
@@ -51,6 +54,7 @@ R extends BensApiResourceName ? BensApiResourcePayload<R> :
R extends ContractInfoApiResourceName ? ContractInfoApiResourcePayload<R> :
R extends GeneralApiResourceName ? GeneralApiResourcePayload<R> :
R extends MetadataApiResourceName ? MetadataApiResourcePayload<R> :
R extends MultichainApiResourceName ? MultichainApiResourcePayload<R> :
R extends RewardsApiResourceName ? RewardsApiResourcePayload<R> :
R extends StatsApiResourceName ? StatsApiResourcePayload<R> :
R extends TacOperationLifecycleApiResourceName ? TacOperationLifecycleApiResourcePayload<R> :
+29 -1
View File
@@ -116,7 +116,7 @@ export const GENERAL_API_ADDRESS_RESOURCES = {
paginated: true,
},
address_epoch_rewards: {
path: '/api/v2/addresses/:hash/election-rewards',
path: '/api/v2/addresses/:hash/celo/election-rewards',
pathParams: [ 'hash' as const ],
filterFields: [],
paginated: true,
@@ -125,6 +125,33 @@ export const GENERAL_API_ADDRESS_RESOURCES = {
path: '/api/v2/proxy/3dparty/xname/addresses/:hash',
pathParams: [ 'hash' as const ],
},
address_3rd_party_info: {
path: '/api/v2/proxy/3dparty/:name',
pathParams: [ 'name' as const ],
filterFields: [ 'address' as const, 'chain_id' as const ],
},
// CSV EXPORTS
address_csv_export_txs: {
path: '/api/v2/addresses/:hash/transactions/csv',
pathParams: [ 'hash' as const ],
},
address_csv_export_internal_txs: {
path: '/api/v2/addresses/:hash/internal-transactions/csv',
pathParams: [ 'hash' as const ],
},
address_csv_export_token_transfers: {
path: '/api/v2/addresses/:hash/token-transfers/csv',
pathParams: [ 'hash' as const ],
},
address_csv_export_logs: {
path: '/api/v2/addresses/:hash/logs/csv',
pathParams: [ 'hash' as const ],
},
address_csv_export_celo_election_rewards: {
path: '/api/v2/addresses/:hash/celo/election-rewards/csv',
pathParams: [ 'hash' as const ],
},
} satisfies Record<string, ApiResource>;
export type GeneralApiAddressResourceName = `general:${ keyof typeof GENERAL_API_ADDRESS_RESOURCES }`;
@@ -149,6 +176,7 @@ R extends 'general:address_collections' ? AddressCollectionsResponse :
R extends 'general:address_withdrawals' ? AddressWithdrawalsResponse :
R extends 'general:address_epoch_rewards' ? AddressEpochRewardsResponse :
R extends 'general:address_xstar_score' ? AddressXStarResponse :
R extends 'general:address_3rd_party_info' ? unknown :
never;
/* eslint-enable @stylistic/indent */
-15
View File
@@ -6,8 +6,6 @@ import type {
BlockFilters,
BlockWithdrawalsResponse,
BlockCountdownResponse,
BlockEpoch,
BlockEpochElectionRewardDetailsResponse,
BlockInternalTransactionsResponse,
} from 'types/api/block';
import type { TTxsWithBlobsFilters } from 'types/api/txsFilters';
@@ -39,17 +37,6 @@ export const GENERAL_API_BLOCK_RESOURCES = {
filterFields: [],
paginated: true,
},
block_epoch: {
path: '/api/v2/blocks/:height_or_hash/epoch',
pathParams: [ 'height_or_hash' as const ],
filterFields: [],
},
block_election_rewards: {
path: '/api/v2/blocks/:height_or_hash/election-rewards/:reward_type',
pathParams: [ 'height_or_hash' as const, 'reward_type' as const ],
filterFields: [],
paginated: true,
},
} satisfies Record<string, ApiResource>;
export type GeneralApiBlockResourceName = `general:${ keyof typeof GENERAL_API_BLOCK_RESOURCES }`;
@@ -62,8 +49,6 @@ R extends 'general:block_countdown' ? BlockCountdownResponse :
R extends 'general:block_txs' ? BlockTransactionsResponse :
R extends 'general:block_internal_txs' ? BlockInternalTransactionsResponse :
R extends 'general:block_withdrawals' ? BlockWithdrawalsResponse :
R extends 'general:block_epoch' ? BlockEpoch :
R extends 'general:block_election_rewards' ? BlockEpochElectionRewardDetailsResponse :
never;
/* eslint-enable @stylistic/indent */
+25 -6
View File
@@ -7,7 +7,8 @@ import type {
import type { Blob } from 'types/api/blobs';
import type { Block } from 'types/api/block';
import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts';
import type { BackendVersionConfig, CsvExportConfig } from 'types/api/configs';
import type { BackendVersionConfig, CeloConfig, CsvExportConfig } from 'types/api/configs';
import type { CeloEpochDetails, CeloEpochElectionRewardDetailsResponse, CeloEpochListResponse } from 'types/api/epochs';
import type { IndexingStatus } from 'types/api/indexingStatus';
import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves';
import type {
@@ -181,6 +182,23 @@ export const GENERAL_API_MISC_RESOURCES = {
pathParams: [ 'hash' as const ],
},
// EPOCHS
epochs_celo: {
path: '/api/v2/celo/epochs',
filterFields: [],
paginated: true,
},
epoch_celo: {
path: '/api/v2/celo/epochs/:number',
pathParams: [ 'number' as const ],
},
epoch_celo_election_rewards: {
path: '/api/v2/celo/epochs/:number/election-rewards/:reward_type',
pathParams: [ 'number' as const, 'reward_type' as const ],
filterFields: [],
paginated: true,
},
// ADVANCED FILTER
advanced_filter: {
path: '/api/v2/advanced-filters',
@@ -224,11 +242,8 @@ export const GENERAL_API_MISC_RESOURCES = {
config_csv_export: {
path: '/api/v2/config/csv-export',
},
// CSV EXPORT
csv_export_token_holders: {
path: '/api/v2/tokens/:hash/holders/csv',
pathParams: [ 'hash' as const ],
config_celo: {
path: '/api/v2/config/celo',
},
// OTHER
@@ -261,6 +276,7 @@ R extends 'general:search' ? SearchResult :
R extends 'general:search_check_redirect' ? SearchRedirectResult :
R extends 'general:config_backend_version' ? BackendVersionConfig :
R extends 'general:config_csv_export' ? CsvExportConfig :
R extends 'general:config_celo' ? CeloConfig :
R extends 'general:blob' ? Blob :
R extends 'general:validators_stability' ? ValidatorsStabilityResponse :
R extends 'general:validators_stability_counters' ? ValidatorsStabilityCountersResponse :
@@ -268,6 +284,9 @@ R extends 'general:validators_blackfort' ? ValidatorsBlackfortResponse :
R extends 'general:validators_blackfort_counters' ? ValidatorsBlackfortCountersResponse :
R extends 'general:validators_zilliqa' ? ValidatorsZilliqaResponse :
R extends 'general:validator_zilliqa' ? ValidatorZilliqa :
R extends 'general:epochs_celo' ? CeloEpochListResponse :
R extends 'general:epoch_celo' ? CeloEpochDetails :
R extends 'general:epoch_celo_election_rewards' ? CeloEpochElectionRewardDetailsResponse :
R extends 'general:user_ops' ? UserOpsResponse :
R extends 'general:user_op' ? UserOp :
R extends 'general:user_ops_account' ? UserOpsAccount :
+4
View File
@@ -49,6 +49,10 @@ export const GENERAL_API_TOKEN_RESOURCES = {
filterFields: [ 'q' as const, 'chain_ids' as const ],
paginated: true,
},
token_csv_export_holders: {
path: '/api/v2/tokens/:hash/holders/csv',
pathParams: [ 'hash' as const ],
},
// TOKEN INSTANCE
token_instance: {
-15
View File
@@ -2,21 +2,6 @@ import type { ApiResource } from '../../types';
import type { BlockCountdownResponse } from 'types/api/block';
export const GENERAL_API_V1_RESOURCES = {
csv_export_txs: {
path: '/api/v1/transactions-csv',
},
csv_export_internal_txs: {
path: '/api/v1/internal-transactions-csv',
},
csv_export_token_transfers: {
path: '/api/v1/token-transfers-csv',
},
csv_export_logs: {
path: '/api/v1/logs-csv',
},
csv_export_epoch_rewards: {
path: '/api/v1/celo-election-rewards-csv',
},
graphql: {
path: '/api/v1/graphql',
},
+3
View File
@@ -11,6 +11,9 @@ export const METADATA_API_RESOURCES = {
public_tag_types: {
path: '/api/v1/public-tag-types',
},
address_submit: {
path: '/api/v1/addresses\\:submit',
},
} satisfies Record<string, ApiResource>;
export type MetadataApiResourceName = `metadata:${ keyof typeof METADATA_API_RESOURCES }`;
+17
View File
@@ -0,0 +1,17 @@
import type { ApiResource } from '../types';
import type * as multichain from '@blockscout/multichain-aggregator-types';
export const MULTICHAIN_API_RESOURCES = {
interop_messages: {
path: '/api/v1/interop/messages',
paginated: true,
},
} satisfies Record<string, ApiResource>;
export type MultichainApiResourceName = `multichain:${ keyof typeof MULTICHAIN_API_RESOURCES }`;
/* eslint-disable @stylistic/indent */
export type MultichainApiResourcePayload<R extends MultichainApiResourceName> =
R extends 'multichain:interop_messages' ? multichain.ListInteropMessagesResponse :
never;
/* eslint-enable @stylistic/indent */
+1 -1
View File
@@ -1,4 +1,4 @@
export type ApiName = 'general' | 'admin' | 'bens' | 'contractInfo' | 'metadata' | 'rewards' | 'stats' | 'visualize' | 'tac';
export type ApiName = 'general' | 'admin' | 'bens' | 'contractInfo' | 'metadata' | 'multichain' | 'rewards' | 'stats' | 'tac' | 'visualize';
export interface ApiResource {
path: string;
+6 -3
View File
@@ -3,6 +3,7 @@ import { omit, pickBy } from 'es-toolkit';
import React from 'react';
import type { CsrfData } from 'types/client/account';
import type { ChainConfig } from 'types/multichain';
import config from 'configs/app';
import isBodyAllowed from 'lib/api/isBodyAllowed';
@@ -21,21 +22,23 @@ export interface Params<R extends ResourceName> {
queryParams?: Record<string, string | Array<string> | number | boolean | undefined | null>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal' | 'headers'>;
logError?: boolean;
chain?: ChainConfig;
}
export default function useApiFetch() {
const fetch = useFetch();
const queryClient = useQueryClient();
const { token: csrfToken } = queryClient.getQueryData<CsrfData>(getResourceKey('general:csrf')) || {};
return React.useCallback(<R extends ResourceName, SuccessType = unknown, ErrorType = unknown>(
resourceName: R,
{ pathParams, queryParams, fetchParams, logError }: Params<R> = {},
{ pathParams, queryParams, fetchParams, logError, chain }: Params<R> = {},
) => {
const apiToken = cookies.get(cookies.NAMES.API_TOKEN);
const { api, apiName, resource } = getResourceParams(resourceName);
const url = buildUrl(resourceName, pathParams, queryParams);
const { api, apiName, resource } = getResourceParams(resourceName, chain);
const url = buildUrl(resourceName, pathParams, queryParams, undefined, chain);
const withBody = isBodyAllowed(fetchParams?.method);
const headers = pickBy({
'x-endpoint': api.endpoint && apiName !== 'general' && isNeedProxy() ? api.endpoint : undefined,
+16 -6
View File
@@ -1,6 +1,8 @@
import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import multichainConfig from 'configs/multichain';
import { useMultichainContext } from 'lib/contexts/multichain';
import type { Params as FetchParams } from 'lib/hooks/useFetch';
import type { ResourceError, ResourceName, ResourcePathParams, ResourcePayload } from './resources';
@@ -12,29 +14,37 @@ export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'headers'>;
queryOptions?: Partial<Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryFn'>>;
logError?: boolean;
chainSlug?: string;
}
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
export interface GetResourceKeyParams<R extends ResourceName, E = unknown, D = ResourcePayload<R>>
extends Pick<Params<R, E, D>, 'pathParams' | 'queryParams'> {
chainSlug?: string;
}
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams, chainSlug }: GetResourceKeyParams<R> = {}) {
if (pathParams || queryParams) {
return [ resource, { ...pathParams, ...queryParams } ];
return [ resource, chainSlug, { ...pathParams, ...queryParams } ].filter(Boolean);
}
return [ resource ];
return [ resource, chainSlug ].filter(Boolean);
}
export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>(
resource: R,
{ queryOptions, pathParams, queryParams, fetchParams, logError }: Params<R, E, D> = {},
{ queryOptions, pathParams, queryParams, fetchParams, logError, chainSlug }: Params<R, E, D> = {},
) {
const apiFetch = useApiFetch();
const { chain } = useMultichainContext() ||
{ chain: chainSlug ? multichainConfig()?.chains.find((chain) => chain.slug === chainSlug) : undefined };
return useQuery<ResourcePayload<R>, ResourceError<E>, D>({
queryKey: queryOptions?.queryKey || getResourceKey(resource, { pathParams, queryParams }),
queryKey: queryOptions?.queryKey || getResourceKey(resource, { pathParams, queryParams, chainSlug: chain?.slug }),
queryFn: async({ signal }) => {
// all errors and error typing is handled by react-query
// so error response will never go to the data
// that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>"
return apiFetch(resource, { pathParams, queryParams, logError, fetchParams: { ...fetchParams, signal } }) as Promise<ResourcePayload<R>>;
return apiFetch(resource, { pathParams, queryParams, chain, logError, fetchParams: { ...fetchParams, signal } }) as Promise<ResourcePayload<R>>;
},
...queryOptions,
});
+68
View File
@@ -0,0 +1,68 @@
import { useRouter } from 'next/router';
import React from 'react';
import type { ChainConfig } from 'types/multichain';
import multichainConfig from 'configs/multichain';
import getQueryParamString from 'lib/router/getQueryParamString';
interface MultichainProviderProps {
children: React.ReactNode;
chainSlug?: string;
}
export interface TMultichainContext {
chain: ChainConfig;
}
export const MultichainContext = React.createContext<TMultichainContext | null>(null);
export function MultichainProvider({ children, chainSlug: chainSlugProp }: MultichainProviderProps) {
const router = useRouter();
const chainSlugQueryParam = router.pathname.includes('chain-slug') ? getQueryParamString(router.query['chain-slug']) : undefined;
const [ chainSlug, setChainSlug ] = React.useState<string | undefined>(chainSlugProp ?? chainSlugQueryParam);
React.useEffect(() => {
if (chainSlugProp) {
setChainSlug(chainSlugProp);
}
}, [ chainSlugProp ]);
const chain = React.useMemo(() => {
const config = multichainConfig();
if (!config) {
return;
}
if (!chainSlug) {
return;
}
return config.chains.find((chain) => chain.slug === chainSlug);
}, [ chainSlug ]);
const value = React.useMemo(() => {
if (!chain) {
return null;
}
return {
chain,
};
}, [ chain ]);
return (
<MultichainContext.Provider value={ value }>
{ children }
</MultichainContext.Provider>
);
}
export function useMultichainContext(disabled: boolean = !multichainConfig) {
const context = React.useContext(MultichainContext);
if (context === undefined || disabled) {
return null;
}
return context;
}
+5 -4
View File
@@ -54,6 +54,9 @@ export default function useFetch() {
const error = {
status: response.status,
statusText: response.statusText,
rateLimits: {
bypassOptions: response.headers.get('bypass-429-option'),
},
};
if (meta?.logError && rollbar) {
@@ -67,18 +70,16 @@ export default function useFetch() {
if (!isJson) {
return response.text().then(
(textError) => Promise.reject({
...error,
payload: textError,
status: response.status,
statusText: response.statusText,
}),
);
}
return response.json().then(
(jsonError) => Promise.reject({
...error,
payload: jsonError as Error,
status: response.status,
statusText: response.statusText,
}),
() => {
return Promise.reject(error);
+7
View File
@@ -118,6 +118,12 @@ export default function useNavItems(): ReturnType {
icon: 'MUD_menu',
isActive: pathname === '/mud-worlds',
} : null;
const epochs = config.features.celo.isEnabled ? {
text: 'Epochs',
nextRoute: { pathname: '/epochs' as const },
icon: 'hourglass',
isActive: pathname.startsWith('/epochs'),
} : null;
const rollupFeature = config.features.rollup;
@@ -196,6 +202,7 @@ export default function useNavItems(): ReturnType {
internalTxs,
userOps,
blocks,
epochs,
topAccounts,
validators,
verifiedContracts,
+2
View File
@@ -76,6 +76,8 @@ export default function useTimeAgoIncrement(ts: string | number | null, isEnable
timeouts.push(endTimeoutId);
};
setValue(dayjs(ts).fromNow());
isEnabled && startIncrement();
!isEnabled && setValue(dayjs(ts).fromNow());
+6
View File
@@ -54,6 +54,8 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/name-domains/[name]': 'Regular page',
'/validators': 'Root page',
'/validators/[id]': 'Regular page',
'/epochs': 'Root page',
'/epochs/[number]': 'Regular page',
'/gas-tracker': 'Root page',
'/mud-worlds': 'Root page',
'/token-transfers': 'Root page',
@@ -61,6 +63,10 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/pools': 'Root page',
'/pools/[hash]': 'Regular page',
'/interop-messages': 'Root page',
'/chain/[chain-slug]/accounts/label/[slug]': 'Root page',
'/chain/[chain-slug]/address/[hash]': 'Regular page',
'/chain/[chain-slug]/block/[height_or_hash]': 'Regular page',
'/chain/[chain-slug]/tx/[hash]': 'Regular page',
'/operations': 'Root page',
'/operation/[id]': 'Regular page',
+6
View File
@@ -57,6 +57,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/name-domains/[name]': DEFAULT_TEMPLATE,
'/validators': DEFAULT_TEMPLATE,
'/validators/[id]': DEFAULT_TEMPLATE,
'/epochs': DEFAULT_TEMPLATE,
'/epochs/[number]': DEFAULT_TEMPLATE,
'/gas-tracker': 'Explore real-time %network_title% gas fees with Blockscout\'s advanced gas fee tracker. Get accurate %network_gwei% estimates and track transaction costs live.',
'/mud-worlds': DEFAULT_TEMPLATE,
'/token-transfers': DEFAULT_TEMPLATE,
@@ -64,6 +66,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/pools': DEFAULT_TEMPLATE,
'/pools/[hash]': DEFAULT_TEMPLATE,
'/interop-messages': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/accounts/label/[slug]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/address/[hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/block/[height_or_hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/tx/[hash]': DEFAULT_TEMPLATE,
'/operations': DEFAULT_TEMPLATE,
'/operation/[id]': DEFAULT_TEMPLATE,
+6
View File
@@ -54,6 +54,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/name-domains/[name]': '%network_name% %name% domain details',
'/validators': '%network_name% validators list',
'/validators/[id]': '%network_name% validator %id% details',
'/epochs': '%network_name% epochs',
'/epochs/[number]': '%network_name% epoch %number% details',
'/gas-tracker': 'Track %network_name% gas fees in %network_gwei%',
'/mud-worlds': '%network_name% MUD worlds list',
'/token-transfers': '%network_name% token transfers',
@@ -61,6 +63,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/pools': '%network_name% DEX pools',
'/pools/[hash]': '%network_name% pool details',
'/interop-messages': '%network_name% interop messages',
'/chain/[chain-slug]/accounts/label/[slug]': '%network_name% addresses search by label',
'/chain/[chain-slug]/address/[hash]': '%network_name% address details for %hash%',
'/chain/[chain-slug]/block/[height_or_hash]': '%network_name% block %height_or_hash% details',
'/chain/[chain-slug]/tx/[hash]': '%network_name% transaction %hash% details',
'/operations': '%network_name% operations',
'/operation/[id]': '%network_name% operation %id%',
+6
View File
@@ -52,6 +52,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/name-domains/[name]': 'Domain details',
'/validators': 'Validators list',
'/validators/[id]': 'Validator details',
'/epochs': 'Epochs',
'/epochs/[number]': 'Epoch details',
'/gas-tracker': 'Gas tracker',
'/mud-worlds': 'MUD worlds',
'/token-transfers': 'Token transfers',
@@ -59,6 +61,10 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/pools': 'DEX pools',
'/pools/[hash]': 'Pool details',
'/interop-messages': 'Interop messages',
'/chain/[chain-slug]/accounts/label/[slug]': 'Chain addresses search by label',
'/chain/[chain-slug]/address/[hash]': 'Chain address details',
'/chain/[chain-slug]/block/[height_or_hash]': 'Chain block details',
'/chain/[chain-slug]/tx/[hash]': 'Chain transaction details',
'/operations': 'Operations',
'/operation/[id]': 'Operation details',
+1
View File
@@ -27,6 +27,7 @@ export default function useMixpanelInit() {
const mixpanelConfig: Partial<Config> = {
debug: Boolean(debugFlagQuery.current || debugFlagCookie),
persistence: 'localStorage',
};
const isAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN));
+4
View File
@@ -24,6 +24,7 @@ export enum EventTypes {
BUTTON_CLICK = 'Button click',
PROMO_BANNER = 'Promo banner',
APP_FEEDBACK = 'App feedback',
ADDRESS_WIDGET = 'Address widget',
}
/* eslint-disable @stylistic/indent */
@@ -169,5 +170,8 @@ Type extends EventTypes.APP_FEEDBACK ? {
AppId: string;
Score: number;
} :
Type extends EventTypes.ADDRESS_WIDGET ? {
Name: string;
} :
undefined;
/* eslint-enable @stylistic/indent */
+137
View File
@@ -0,0 +1,137 @@
import { useRouter } from 'next/router';
import React from 'react';
import getQueryParamString from './getQueryParamString';
export default function useEtherscanRedirects() {
const router = useRouter();
// The browser does not send the segment hash (or anchor) to the server,
// so we have to handle such redirects for the etherscan-like links on the client side.
React.useEffect(() => {
const segmentHash = window.location.hash;
if (segmentHash) {
switch (router.route) {
case '/tx/[hash]': {
const hash = getQueryParamString(router.query.hash);
switch (segmentHash) {
case '#statechange':
router.replace({
pathname: '/tx/[hash]',
query: { hash, tab: 'state' },
});
break;
case '#eventlog':
router.replace({
pathname: '/tx/[hash]',
query: { hash, tab: 'logs' },
});
break;
case '#aa':
router.replace({
pathname: '/tx/[hash]',
query: { hash, tab: 'user_ops' },
});
break;
case '#internal':
router.replace({
pathname: '/tx/[hash]',
query: { hash, tab: 'internal' },
});
break;
}
break;
}
case '/address/[hash]': {
const hash = getQueryParamString(router.query.hash);
switch (segmentHash) {
case '#internaltx':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'internal_txns' },
});
break;
case '#tokentxns':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'token_transfers' },
});
break;
case '#asset-tokens':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'tokens' },
});
break;
case '#asset-nfts':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'tokens_nfts' },
});
break;
case '#aatx':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'user_ops' },
});
break;
case '#code':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'contract' },
});
break;
case '#readContract':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'read_contract' },
});
break;
case '#writeContract':
router.replace({
pathname: '/address/[hash]',
query: { hash, tab: 'write_contract' },
});
break;
}
break;
}
case '/token/[hash]': {
const hash = getQueryParamString(router.query.hash);
switch (segmentHash) {
case '#balances':
router.replace({
pathname: '/token/[hash]',
query: { hash, tab: 'holders' },
});
break;
}
break;
}
case '/stats': {
switch (segmentHash) {
case '#section-contracts-data':
router.replace({
pathname: '/stats',
hash: 'contracts',
});
break;
}
break;
}
default:
break;
}
}
// run only on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
}
+3 -1
View File
@@ -7,5 +7,7 @@ export default function shortenString(string: string | null, charNumber: number
return string;
}
return string.slice(0, charNumber - 4) + '...' + string.slice(-4);
const tailLength = charNumber < 8 ? 2 : 4;
return string.slice(0, charNumber - tailLength) + '...' + string.slice(-tailLength);
}
+14 -3
View File
@@ -1,9 +1,14 @@
// https://hexdocs.pm/phoenix/js/
import type { SocketConnectOption } from 'phoenix';
import type { Channel, SocketConnectOption } from 'phoenix';
import { Socket } from 'phoenix';
import React, { useEffect, useState } from 'react';
export const SocketContext = React.createContext<Socket | null>(null);
type ChannelRegistry = Record<string, { channel: Channel; subscribers: number }>;
export const SocketContext = React.createContext<{
socket: Socket | null;
channelRegistry: React.MutableRefObject<ChannelRegistry>;
} | null>(null);
interface SocketProviderProps {
children: React.ReactNode;
@@ -13,6 +18,7 @@ interface SocketProviderProps {
export function SocketProvider({ children, options, url }: SocketProviderProps) {
const [ socket, setSocket ] = useState<Socket | null>(null);
const channelRegistry = React.useRef<ChannelRegistry>({});
useEffect(() => {
if (!url) {
@@ -29,8 +35,13 @@ export function SocketProvider({ children, options, url }: SocketProviderProps)
};
}, [ options, url ]);
const value = React.useMemo(() => ({
socket,
channelRegistry,
}), [ socket, channelRegistry ]);
return (
<SocketContext.Provider value={ socket }>
<SocketContext.Provider value={ value }>
{ children }
</SocketContext.Provider>
);
+5
View File
@@ -1,5 +1,6 @@
import type { Channel } from 'phoenix';
import type * as multichain from '@blockscout/multichain-aggregator-types';
import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'types/api/address';
import type { NewArbitrumBatchSocketResponse } from 'types/api/arbitrumL2';
import type { NewBlockSocketResponse } from 'types/api/block';
@@ -11,11 +12,13 @@ import type { Transaction } from 'types/api/transaction';
import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2';
export type SocketMessageParams = SocketMessage.NewBlock |
SocketMessage.NewBlockMultichain |
SocketMessage.BlocksIndexStatus |
SocketMessage.InternalTxsIndexStatus |
SocketMessage.TxStatusUpdate |
SocketMessage.TxRawTrace |
SocketMessage.NewTx |
SocketMessage.NewInteropMessage |
SocketMessage.NewPendingTx |
SocketMessage.NewOptimisticDeposits |
SocketMessage.NewArbitrumDeposits |
@@ -49,11 +52,13 @@ interface SocketMessageParamsGeneric<Event extends string | undefined, Payload e
export namespace SocketMessage {
export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>;
export type NewBlockMultichain = SocketMessageParamsGeneric<'new_blocks', Array<{ block_number: number; chain_id: number }>>;
export type BlocksIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>;
export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>;
export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>;
export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewInteropMessage = SocketMessageParamsGeneric<'new_messages', Array<multichain.InteropMessage>>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type NewOptimisticDeposits = SocketMessageParamsGeneric<'new_optimism_deposits', { deposits: number }>;
export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>;
+13 -13
View File
@@ -3,8 +3,6 @@ import { useEffect, useRef, useState } from 'react';
import { useSocket } from './context';
const CHANNEL_REGISTRY: Record<string, { channel: Channel; subscribers: number }> = {};
interface Params {
topic: string | undefined;
params?: object;
@@ -15,7 +13,7 @@ interface Params {
}
export default function useSocketChannel({ topic, params, isDisabled, onJoin, onSocketClose, onSocketError }: Params) {
const socket = useSocket();
const { socket, channelRegistry } = useSocket() || {};
const [ channel, setChannel ] = useState<Channel>();
const onCloseRef = useRef<string>(undefined);
const onErrorRef = useRef<string>(undefined);
@@ -47,18 +45,18 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
}, [ channel, isDisabled ]);
useEffect(() => {
if (socket === null || isDisabled || !topic) {
if (!socket || isDisabled || !topic || !channelRegistry) {
return;
}
let ch: Channel;
if (CHANNEL_REGISTRY[topic]) {
ch = CHANNEL_REGISTRY[topic].channel;
CHANNEL_REGISTRY[topic].subscribers++;
if (channelRegistry.current[topic]) {
ch = channelRegistry.current[topic].channel;
channelRegistry.current[topic].subscribers++;
onJoinRef.current?.(ch, '');
} else {
ch = socket.channel(topic);
CHANNEL_REGISTRY[topic] = { channel: ch, subscribers: 1 };
channelRegistry.current[topic] = { channel: ch, subscribers: 1 };
ch.join()
.receive('ok', (message) => onJoinRef.current?.(ch, message))
.receive('error', () => {
@@ -68,18 +66,20 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
setChannel(ch);
const currentRegistry = channelRegistry.current;
return () => {
if (CHANNEL_REGISTRY[topic]) {
CHANNEL_REGISTRY[topic].subscribers > 0 && CHANNEL_REGISTRY[topic].subscribers--;
if (CHANNEL_REGISTRY[topic].subscribers === 0) {
if (currentRegistry[topic]) {
currentRegistry[topic].subscribers > 0 && currentRegistry[topic].subscribers--;
if (currentRegistry[topic].subscribers === 0) {
ch.leave();
delete CHANNEL_REGISTRY[topic];
delete currentRegistry[topic];
}
}
setChannel(undefined);
};
}, [ socket, topic, params, isDisabled, onSocketError ]);
}, [ socket, topic, params, isDisabled, onSocketError, channelRegistry ]);
return channel;
}
+35 -20
View File
@@ -1,31 +1,36 @@
import { type Chain } from 'viem';
import config from 'configs/app';
import appConfig from 'configs/app';
import multichainConfig from 'configs/multichain';
export const currentChain: Chain = {
id: Number(config.chain.id),
name: config.chain.name ?? '',
nativeCurrency: {
decimals: config.chain.currency.decimals,
name: config.chain.currency.name ?? '',
symbol: config.chain.currency.symbol ?? '',
},
rpcUrls: {
'default': {
http: config.chain.rpcUrls,
const getChainInfo = (config: typeof appConfig = appConfig) => {
return {
id: Number(config.chain.id),
name: config.chain.name ?? '',
nativeCurrency: {
decimals: config.chain.currency.decimals,
name: config.chain.currency.name ?? '',
symbol: config.chain.currency.symbol ?? '',
},
},
blockExplorers: {
'default': {
name: 'Blockscout',
url: config.app.baseUrl,
rpcUrls: {
'default': {
http: config.chain.rpcUrls,
},
},
},
testnet: config.chain.isTestnet,
blockExplorers: {
'default': {
name: 'Blockscout',
url: config.app.baseUrl,
},
},
testnet: config.chain.isTestnet,
};
};
export const currentChain: Chain | undefined = !appConfig.features.opSuperchain.isEnabled ? getChainInfo() : undefined;
export const parentChain: Chain | undefined = (() => {
const rollupFeature = config.features.rollup;
const rollupFeature = appConfig.features.rollup;
const parentChain = rollupFeature.isEnabled && rollupFeature.parentChain;
@@ -55,3 +60,13 @@ export const parentChain: Chain | undefined = (() => {
testnet: parentChain.isTestnet,
};
})();
export const clusterChains: Array<Chain> | undefined = (() => {
const config = multichainConfig();
if (!config) {
return;
}
return config.chains.map(({ config }) => getChainInfo(config)).filter(Boolean);
})();
+2 -1
View File
@@ -3,7 +3,8 @@ import { createPublicClient, http } from 'viem';
import { currentChain } from './chains';
export const publicClient = (() => {
if (currentChain.rpcUrls.default.http.filter(Boolean).length === 0) {
// TODO @tom2drum public clients for multichain (currently used only in degradation views)
if (currentChain?.rpcUrls.default.http.filter(Boolean).length === 0) {
return;
}
+42 -11
View File
@@ -1,15 +1,48 @@
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi';
import type { AppKitNetwork } from '@reown/appkit/networks';
import type { Chain } from 'viem';
import type { Chain, Transport } from 'viem';
import { fallback, http } from 'viem';
import { createConfig } from 'wagmi';
import config from 'configs/app';
import { currentChain, parentChain } from 'lib/web3/chains';
import appConfig from 'configs/app';
import multichainConfig from 'configs/multichain';
import { currentChain, parentChain, clusterChains } from 'lib/web3/chains';
const feature = config.features.blockchainInteraction;
const feature = appConfig.features.blockchainInteraction;
const chains = [ currentChain, parentChain ].filter(Boolean);
const chains = [ currentChain, parentChain, ...(clusterChains ?? []) ].filter(Boolean);
const getChainTransportFromConfig = (config: typeof appConfig, readOnly?: boolean): Record<string, Transport> => {
if (!config.chain.id) {
return {};
}
return {
[config.chain.id]: fallback(
config.chain.rpcUrls
.concat(readOnly ? `${ config.apis.general.endpoint }/api/eth-rpc` : '')
.filter(Boolean)
.map((url) => http(url, { batch: { wait: 100 } })),
),
};
};
const reduceClusterChainsToTransportConfig = (readOnly: boolean): Record<string, Transport> => {
const config = multichainConfig();
if (!config) {
return {};
}
return config.chains
.map(({ config }) => getChainTransportFromConfig(config, readOnly))
.reduce((result, item) => {
Object.entries(item).map(([ id, transport ]) => {
result[id] = transport;
});
return result;
}, {} as Record<string, Transport>);
};
const wagmi = (() => {
@@ -17,12 +50,9 @@ const wagmi = (() => {
const wagmiConfig = createConfig({
chains: chains as [Chain, ...Array<Chain>],
transports: {
[currentChain.id]: fallback(
config.chain.rpcUrls
.map((url) => http(url))
.concat(http(`${ config.apis.general.endpoint }/api/eth-rpc`)),
),
...getChainTransportFromConfig(appConfig, true),
...(parentChain ? { [parentChain.id]: http(parentChain.rpcUrls.default.http[0]) } : {}),
...reduceClusterChainsToTransportConfig(true),
},
ssr: true,
batch: { multicall: { wait: 100 } },
@@ -35,8 +65,9 @@ const wagmi = (() => {
networks: chains as Array<AppKitNetwork>,
multiInjectedProviderDiscovery: true,
transports: {
[currentChain.id]: fallback(config.chain.rpcUrls.map((url) => http(url))),
...getChainTransportFromConfig(appConfig, false),
...(parentChain ? { [parentChain.id]: http() } : {}),
...reduceClusterChainsToTransportConfig(false),
},
projectId: feature.walletConnect.projectId,
ssr: true,
+5 -5
View File
@@ -1,12 +1,10 @@
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import generateCspPolicy from 'nextjs/csp/generateCspPolicy';
import * as csp from 'nextjs/csp/index';
import * as middlewares from 'nextjs/middlewares/index';
const cspPolicy = generateCspPolicy();
export function middleware(req: NextRequest) {
export async function middleware(req: NextRequest) {
const isPageRequest = req.headers.get('accept')?.includes('text/html');
const start = Date.now();
@@ -27,7 +25,9 @@ export function middleware(req: NextRequest) {
const end = Date.now();
res.headers.append('Content-Security-Policy', cspPolicy);
const cspHeader = await csp.get();
res.headers.append('Content-Security-Policy', cspHeader);
res.headers.append('Server-Timing', `middleware;dur=${ end - start }`);
res.headers.append('Docker-ID', process.env.HOSTNAME || '');
+4
View File
@@ -81,6 +81,7 @@ export const token: Address = {
coin_balance: '1',
creation_transaction_hash: '0xc38cf7377bf72d6436f63c37b01b24d032101f20ec1849286dc703c712f10c98',
creator_address_hash: '0x34A9c688512ebdB575e82C50c9803F6ba2916E72',
creation_status: 'success',
exchange_rate: '0.04311',
has_logs: false,
has_token_transfers: true,
@@ -94,6 +95,7 @@ export const eoa: Address = {
coin_balance: '2782650189688719421432220500',
creation_transaction_hash: '0xf2aff6501b632604c39978b47d309813d8a1bcca721864bbe86abf59704f195e',
creator_address_hash: '0x803ad3F50b9e1fF68615e8B053A186e1be288943',
creation_status: null,
exchange_rate: '0.04311',
has_logs: true,
has_token_transfers: false,
@@ -117,6 +119,7 @@ export const contract: Address = {
coin_balance: '27826501896887194214322205',
creation_transaction_hash: '0xf2aff6501b632604c39978b47d309813d8a1bcca721864bbe86abf59704f195e',
creator_address_hash: '0x803ad3F50b9e1fF68615e8B053A186e1be288943',
creation_status: 'success',
exchange_rate: '0.04311',
has_logs: true,
has_token_transfers: false,
@@ -142,6 +145,7 @@ export const validator: Address = {
coin_balance: '22910462800601256910890',
creation_transaction_hash: null,
creator_address_hash: null,
creation_status: null,
exchange_rate: '0.00432018',
has_logs: false,
has_token_transfers: false,
+1 -7
View File
@@ -11,8 +11,6 @@ export const epochRewards: AddressEpochRewardsResponse = {
amount: '136609473658452408568',
account: withName,
associated_account: withName,
block_hash: '0x',
block_number: 26369280,
block_timestamp: '2022-05-15T13:16:24Z',
epoch_number: 1526,
token: tokenInfo,
@@ -22,8 +20,6 @@ export const epochRewards: AddressEpochRewardsResponse = {
amount: '117205842355246195095',
account: withoutName,
associated_account: withoutName,
block_hash: '0x',
block_number: 26352000,
block_timestamp: '2022-05-15T13:16:24Z',
epoch_number: 1525,
token: tokenInfo,
@@ -33,8 +29,6 @@ export const epochRewards: AddressEpochRewardsResponse = {
amount: '125659647325556554060',
account: withEns,
associated_account: withEns,
block_hash: '0x',
block_number: 26300160,
block_timestamp: '2022-05-15T13:16:24Z',
epoch_number: 1524,
token: tokenInfo,
@@ -43,7 +37,7 @@ export const epochRewards: AddressEpochRewardsResponse = {
next_page_params: {
amount: '71952055594478242556',
associated_account_address_hash: '0x30d060f129817c4de5fbc1366d53e19f43c8c64f',
block_number: 25954560,
epoch_number: 25954560,
items_count: 50,
type: 'delegated_payment',
},
+95
View File
@@ -0,0 +1,95 @@
import type { Address3rdPartyWidget } from 'types/views/address';
export const widgets = [
'widget-1',
'widget-2',
'widget-3',
'widget-4',
'widget-5',
'widget-6',
'widget-7',
'widget-8',
'widget-9',
] as const;
export const values = [ 0, 2534783, 75.34, undefined, 1553.5, 100, 0.99, 333, undefined ];
export const config: Record<string, Address3rdPartyWidget> = {
'widget-1': {
name: 'Widget 1',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Value',
hint: 'Hint',
valuePath: 'value',
},
'widget-2': {
name: 'Widget 2',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Another value',
valuePath: 'value',
},
'widget-3': {
name: 'Widget 3',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'One more value',
hint: 'Hint',
valuePath: 'value',
},
'widget-4': {
name: 'Widget 4',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Empty value',
valuePath: 'another_value',
},
'widget-5': {
name: 'Widget 5',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Test value',
hint: 'Hint',
valuePath: 'value',
},
'widget-6': {
name: 'Widget 6',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Another test value',
valuePath: 'value',
},
'widget-7': {
name: 'Widget 7',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'One more test value',
hint: 'Hint',
valuePath: 'value',
},
'widget-8': {
name: 'Widget 8',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Final test value',
valuePath: 'value',
},
'widget-9': {
name: 'Widget 9',
url: 'https://www.example.com',
pages: [ 'eoa', 'contract', 'token' ],
icon: 'http://localhost:3000/widget-logo.png',
title: 'Another empty value',
hint: 'Hint',
valuePath: 'value',
},
};
+1 -1
View File
@@ -168,7 +168,7 @@ export const celo: Block = {
recipient: addressMock.contract,
},
epoch_number: 1486,
is_epoch_block: true,
l1_era_finalized_epoch_number: 1485,
},
};
-57
View File
@@ -1,57 +0,0 @@
import { padStart } from 'es-toolkit/compat';
import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block';
import * as addressMock from '../address/address';
import * as tokenMock from '../tokens/tokenInfo';
import * as tokenTransferMock from '../tokens/tokenTransfer';
export const blockEpoch1: BlockEpoch = {
number: 1486,
distribution: {
carbon_offsetting_transfer: tokenTransferMock.erc20,
community_transfer: tokenTransferMock.erc20,
reserve_bolster_transfer: null,
},
aggregated_election_rewards: {
delegated_payment: {
count: 0,
total: '71210001063118670575',
token: tokenMock.tokenInfoERC20d,
},
group: {
count: 10,
total: '157705500305820107521',
token: tokenMock.tokenInfoERC20b,
},
validator: {
count: 10,
total: '1348139501689262297152',
token: tokenMock.tokenInfoERC20c,
},
voter: {
count: 38,
total: '2244419545166303388',
token: tokenMock.tokenInfoERC20a,
},
},
};
function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails {
return {
amount: `${ 100 - index }210001063118670575`,
account: {
...addressMock.withoutName,
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`,
},
associated_account: {
...addressMock.withoutName,
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`,
},
};
}
export const electionRewardDetails1: BlockEpochElectionRewardDetailsResponse = {
items: Array(15).fill('').map((item, index) => getRewardDetailsItem(index)),
next_page_params: null,
};
+4
View File
@@ -79,6 +79,10 @@ export const FOOTER_LINKS: Array<CustomLinksGroup> = [
{
text: 'MetaDock',
url: 'https://blocksec.com/metadock',
iconUrl: [
'http://localhost:3000/mocks/image_s.jpg',
'http://localhost:3000/mocks/image_svg.svg',
],
},
{
text: 'Sourcify',
+3 -3
View File
@@ -7,6 +7,7 @@ export const verified: SmartContract = {
compiler_version: 'v0.5.16+commit.9c3226ce',
constructor_args: 'constructor_args',
creation_bytecode: 'creation_bytecode',
creation_status: 'success',
deployed_bytecode: 'deployed_bytecode',
compiler_settings: {
evmVersion: 'london',
@@ -33,7 +34,6 @@ export const verified: SmartContract = {
],
language: 'solidity',
license_type: 'gnu_gpl_v3',
is_self_destructed: false,
is_verified_via_eth_bytecode_db: null,
is_changed_bytecode: null,
is_verified_via_sourcify: null,
@@ -88,7 +88,7 @@ export const withProxyAddress: SmartContract = {
export const selfDestructed: SmartContract = {
...verified,
is_self_destructed: true,
creation_status: 'selfdestructed',
};
export const withChangedByteCode: SmartContract = {
@@ -122,7 +122,7 @@ export const nonVerified: SmartContract = {
is_blueprint: false,
creation_bytecode: 'creation_bytecode',
deployed_bytecode: 'deployed_bytecode',
is_self_destructed: false,
creation_status: 'success',
abi: null,
compiler_version: null,
evm_version: null,
+124
View File
@@ -0,0 +1,124 @@
import { padStart } from 'es-toolkit/compat';
import type { CeloEpochDetails, CeloEpochElectionRewardDetails, CeloEpochElectionRewardDetailsResponse, CeloEpochListResponse } from 'types/api/epochs';
import * as addressMock from '../address/address';
import * as tokenMock from '../tokens/tokenInfo';
import * as tokenTransferMock from '../tokens/tokenTransfer';
export const epoch1: CeloEpochDetails = {
number: 1739,
is_finalized: true,
type: 'L1',
timestamp: '2022-06-10T01:27:52.000000Z',
start_block_number: 48477132,
start_processing_block_hash: '0x9dece1eb0e26a95fdf57d2f3a65a6f2e00ca0192e8e3dd157eca0cd323670fa1',
start_processing_block_number: 48563546,
end_processing_block_hash: '0x9dece1eb0e26a95fdf57d2f3a65a6f2e00ca0192e8e3dd157eca0cd323670fa2',
end_processing_block_number: 48563552,
end_block_number: 48563551,
distribution: {
carbon_offsetting_transfer: tokenTransferMock.erc20,
community_transfer: tokenTransferMock.erc20,
transfers_total: {
token: tokenMock.tokenInfoERC20a,
total: {
value: '1000000000000000000',
decimals: '18',
},
},
},
aggregated_election_rewards: {
delegated_payment: {
count: 0,
total: '71210001063118670575',
token: tokenMock.tokenInfoERC20d,
},
group: {
count: 10,
total: '157705500305820107521',
token: tokenMock.tokenInfoERC20b,
},
validator: {
count: 10,
total: '1348139501689262297152',
token: tokenMock.tokenInfoERC20c,
},
voter: {
count: 38,
total: '2244419545166303388',
token: tokenMock.tokenInfoERC20a,
},
},
};
export const epochUnfinalized: CeloEpochDetails = {
number: 1740,
is_finalized: false,
type: 'L2',
timestamp: null,
start_block_number: 48477132,
start_processing_block_hash: null,
start_processing_block_number: null,
end_processing_block_hash: null,
end_processing_block_number: null,
end_block_number: null,
distribution: null,
aggregated_election_rewards: null,
};
export const list: CeloEpochListResponse = {
items: [
{
timestamp: '2022-11-10T01:27:52.000000Z',
number: 1739,
type: 'L2',
is_finalized: false,
start_block_number: 48477132,
end_block_number: null,
distribution: null,
},
{
timestamp: '2022-06-09T01:27:32.000000Z',
number: 1738,
type: 'L1',
is_finalized: true,
end_block_number: 18477131,
start_block_number: 18390714,
distribution: {
carbon_offsetting_transfer: {
decimals: '18',
value: '1723199576750509130678',
},
community_transfer: {
decimals: '18',
value: '68927983070020365227',
},
transfers_total: {
decimals: '18',
value: '1792127559820529495905',
},
},
},
],
next_page_params: null,
};
function getRewardDetailsItem(index: number): CeloEpochElectionRewardDetails {
return {
amount: `${ 100 - index }210001063118670575`,
account: {
...addressMock.withoutName,
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`,
},
associated_account: {
...addressMock.withoutName,
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`,
},
};
}
export const electionRewardDetails1: CeloEpochElectionRewardDetailsResponse = {
items: Array(15).fill('').map((item, index) => getRewardDetailsItem(index)),
next_page_params: null,
};
+1
View File
@@ -15,6 +15,7 @@ function generateCspPolicy() {
descriptors.marketplace(),
descriptors.mixpanel(),
descriptors.monaco(),
descriptors.multichain(),
descriptors.rollbar(),
descriptors.safe(),
descriptors.usernameApi(),
+15
View File
@@ -0,0 +1,15 @@
import appConfig from 'configs/app';
import * as multichainConfig from 'configs/multichain/config.edge';
import generateCspPolicy from './generateCspPolicy';
let cspPolicy: string | undefined = undefined;
export async function get() {
if (!cspPolicy) {
appConfig.features.opSuperchain.isEnabled && await multichainConfig.load();
cspPolicy = generateCspPolicy();
}
return cspPolicy;
}
+1 -1
View File
@@ -41,7 +41,7 @@ export function app(): CspDev.DirectiveDescriptor {
// APIs
...Object.values(config.apis).filter(Boolean).map((api) => api.endpoint),
config.apis.general.socketEndpoint,
...Object.values(config.apis).filter(Boolean).map((api) => api.socketEndpoint),
// chain RPC server
...config.chain.rpcUrls,
+1
View File
@@ -10,6 +10,7 @@ export { helia } from './helia';
export { marketplace } from './marketplace';
export { mixpanel } from './mixpanel';
export { monaco } from './monaco';
export { multichain } from './multichain';
export { rollbar } from './rollbar';
export { safe } from './safe';
export { usernameApi } from './usernameApi';
+26
View File
@@ -0,0 +1,26 @@
import type CspDev from 'csp-dev';
import * as multichainConfig from 'configs/multichain/config.edge';
export function multichain(): CspDev.DirectiveDescriptor {
const value = multichainConfig.getValue();
if (!value) {
return {};
}
const apiEndpoints = value.chains.map((chain) => {
return [
...Object.values(chain.config.apis).filter(Boolean).map((api) => api.endpoint),
...Object.values(chain.config.apis).filter(Boolean).map((api) => api.socketEndpoint),
].filter(Boolean);
}).flat();
const rpcEndpoints = value.chains.map(({ config }) => config.chain.rpcUrls).flat();
return {
'connect-src': [
...apiEndpoints,
...rpcEndpoints,
],
};
}
+37 -2
View File
@@ -6,12 +6,14 @@ import type { RollupType } from 'types/client/rollup';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
const rollupFeature = config.features.rollup;
const adBannerFeature = config.features.adsBanner;
import multichainConfig from 'configs/multichain';
import isNeedProxy from 'lib/api/isNeedProxy';
import * as cookies from 'lib/cookies';
import type * as metadata from 'lib/metadata';
const rollupFeature = config.features.rollup;
const adBannerFeature = config.features.adsBanner;
export interface Props<Pathname extends Route['pathname'] = never> {
query: Route['query'];
cookies: string;
@@ -390,6 +392,16 @@ export const tac: GetServerSideProps<Props> = async(context) => {
return base(context);
};
export const celo: GetServerSideProps<Props> = async(context) => {
if (!config.features.celo.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const interopMessages: GetServerSideProps<Props> = async(context) => {
const rollupFeature = config.features.rollup;
if (!rollupFeature.isEnabled || !rollupFeature.interopEnabled) {
@@ -401,6 +413,29 @@ export const interopMessages: GetServerSideProps<Props> = async(context) => {
return base(context);
};
export const opSuperchain: GetServerSideProps<Props> = async(context) => {
if (!config.features.opSuperchain.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const opSuperchainAccountsLabelSearch: GetServerSideProps<Props> = async(context) => {
const chainSlug = context.params?.['chain-slug'];
const chain = multichainConfig()?.chains.find((chain) => chain.slug === chainSlug);
if (!chain?.config.features.addressMetadata.isEnabled || !context.query.tagType) {
return {
notFound: true,
};
}
return opSuperchain(context);
};
export const pools: GetServerSideProps<Props> = async(context) => {
if (!config.features.pools.isEnabled) {
return {
+6
View File
@@ -38,11 +38,17 @@ declare module "nextjs-routes" {
| DynamicRoute<"/block/countdown/[height]", { "height": string }>
| StaticRoute<"/block/countdown">
| StaticRoute<"/blocks">
| DynamicRoute<"/chain/[chain-slug]/accounts/label/[slug]", { "chain-slug": string; "slug": string }>
| DynamicRoute<"/chain/[chain-slug]/address/[hash]", { "chain-slug": string; "hash": string }>
| DynamicRoute<"/chain/[chain-slug]/block/[height_or_hash]", { "chain-slug": string; "height_or_hash": string }>
| DynamicRoute<"/chain/[chain-slug]/tx/[hash]", { "chain-slug": string; "hash": string }>
| StaticRoute<"/chakra">
| StaticRoute<"/contract-verification">
| StaticRoute<"/csv-export">
| StaticRoute<"/deposits">
| StaticRoute<"/dispute-games">
| DynamicRoute<"/epochs/[number]", { "number": string }>
| StaticRoute<"/epochs">
| StaticRoute<"/gas-tracker">
| StaticRoute<"/graphiql">
| StaticRoute<"/">
+79 -2
View File
@@ -1,4 +1,4 @@
const oldUrls = [
const OLD_UI_URLS = [
// ACCOUNT
{
source: '/account/tag_address',
@@ -266,9 +266,86 @@ const oldUrls = [
},
];
const ETHERSCAN_URLS = [
{
source: '/txsAA',
destination: '/ops',
},
{
source: '/txs',
has: [
{ type: 'query', key: 'block' },
],
destination: '/block/:block?tab=txs',
},
{
source: '/txsInternal',
has: [
{ type: 'query', key: 'block' },
],
destination: '/block/:block?tab=internal_txs',
},
{
source: '/txsInternal',
destination: '/internal-txs',
},
{
source: '/blocks_forked',
destination: '/blocks?tab=reorgs',
},
{
source: '/contractsVerified',
destination: '/verified-contracts',
},
{
source: '/verifyContract',
has: [
{ type: 'query', key: 'a' },
],
destination: '/address/:a/contract-verification',
},
{
source: '/verifyContract',
destination: '/contract-verification',
},
{
source: '/tokentxns',
destination: '/token-transfers',
},
{
source: '/nft/:hash/:id',
destination: '/token/:hash/instance/:id',
},
{
source: '/charts',
destination: '/stats',
},
{
source: '/nft-latest-mints',
destination: '/advanced-filter?transaction_types=ERC-1155%2CERC-721&methods=0xa0712d68&methods_names=mint',
},
{
source: '/nft-transfers',
destination: '/advanced-filter?transaction_types=ERC-1155%2CERC-721',
},
{
source: '/name-lookup-search',
destination: '/name-domains',
},
{
source: '/txsExit',
destination: '/withdrawals',
},
{
source: '/txsEnqueued',
destination: '/deposits',
},
];
async function redirects() {
return [
...oldUrls.map((item) => ({ ...item, permanent: false })),
...OLD_UI_URLS.map((item) => ({ ...item, permanent: false })),
...ETHERSCAN_URLS.map((item) => ({ ...item, permanent: true })),
];
}
+16
View File
@@ -0,0 +1,16 @@
import type { Route } from 'nextjs-routes';
import { route as nextjsRoute } from 'nextjs-routes';
import type { TMultichainContext } from 'lib/contexts/multichain';
export const route = (route: Route, multichainContext?: TMultichainContext | null) => {
return nextjsRoute(routeParams(route, multichainContext));
};
export const routeParams = (route: Route, multichainContext?: TMultichainContext | null): Route => {
if (multichainContext) {
const pathname = '/chain/[chain-slug]' + route.pathname;
return { ...route, pathname, query: { ...route.query, 'chain-slug': multichainContext.chain.slug } } as Route;
}
return route;
};
+2
View File
@@ -23,6 +23,8 @@ export default function fetchFactory(
cookie,
...pick(_req.headers, [
'x-csrf-token',
'recaptcha-v2-response',
'user-agent',
'Authorization', // the old value, just in case
'authorization', // Node.js automatically lowercases headers
// feature flags
+2 -1
View File
@@ -13,7 +13,7 @@
"dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js",
"build": "next build",
"build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && yarn svg:build-sprite && ./deploy/scripts/make_envs_script.sh && next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./",
"build:docker": "./tools/scripts/build.docker.sh",
"start": "next start",
"start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend:local",
"start:docker:preset": "./tools/scripts/docker.preset.sh",
@@ -43,6 +43,7 @@
},
"dependencies": {
"@blockscout/bens-types": "1.4.1",
"@blockscout/multichain-aggregator-types": "1.6.0-alpha.0",
"@blockscout/points-types": "1.3.0-alpha.2",
"@blockscout/stats-types": "^2.9.0",
"@blockscout/tac-operation-lifecycle-types": "0.0.1-alpha.6",

Some files were not shown because too many files have changed in this diff Show More