mirror of
https://github.com/luxfi/explore.git
synced 2026-07-27 03:13:50 +00:00
OP Superchain Explorer: MVP (#2786)
* context for storing subchain config and tx view for subchain * address page and block page for subchain * address aggregated page: local txs tab * connect MultichainSelect to context * home page placeholder * refactor subchain select state * envs for demo * fix ts * clear value for l2 review * subchain widgets on home page * csp policies * sockets, duck and goose * fix socket on subchain address page * link builder for subchain views * update home widget * fix time increment * enable tx interpretator and metadata service * generate multichain config based on every chain app config * Fix the multichain config to work in Docker * multichain socket test * rename subchain-id to subchain-slug path param * refactoring * update chain icons on entities * home page: latest local txs * latest cross-chain txs * minor improvements * renaming, pt. 1 * rename chain routes * enable blockchain interaction * add loading state to icon shield * fix build * fix tests * update types package
This commit is contained in:
@@ -25,6 +25,7 @@ on:
|
||||
- neon_devnet
|
||||
- optimism
|
||||
- optimism_sepolia
|
||||
- optimism_superchain
|
||||
- polygon
|
||||
- rootstock
|
||||
- scroll_sepolia
|
||||
|
||||
@@ -27,6 +27,7 @@ on:
|
||||
- neon_devnet
|
||||
- optimism
|
||||
- optimism_sepolia
|
||||
- optimism_superchain
|
||||
- polygon
|
||||
- rari_testnet
|
||||
- rootstock
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
@@ -377,6 +377,7 @@
|
||||
"optimism",
|
||||
"optimism_interop_0",
|
||||
"optimism_sepolia",
|
||||
"optimism_superchain",
|
||||
"polygon",
|
||||
"rari_testnet",
|
||||
"rootstock_testnet",
|
||||
|
||||
+14
@@ -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
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -26,6 +26,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';
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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/aix-ppc64@0.25.5":
|
||||
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/android-arm64@0.25.5":
|
||||
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/android-arm@0.25.5":
|
||||
version "0.25.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz#4290d6d3407bae3883ad2cded1081a234473ce26"
|
||||
integrity sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==
|
||||
|
||||
"@esbuild/android-x64@0.25.5":
|
||||
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/darwin-arm64@0.25.5":
|
||||
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/darwin-x64@0.25.5":
|
||||
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/freebsd-arm64@0.25.5":
|
||||
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/freebsd-x64@0.25.5":
|
||||
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/linux-arm64@0.25.5":
|
||||
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/linux-arm@0.25.5":
|
||||
version "0.25.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz#2a0be71b6cd8201fa559aea45598dffabc05d911"
|
||||
integrity sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==
|
||||
|
||||
"@esbuild/linux-ia32@0.25.5":
|
||||
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/linux-loong64@0.25.5":
|
||||
version "0.25.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz#428cf2213ff786a502a52c96cf29d1fcf1eb8506"
|
||||
integrity sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==
|
||||
|
||||
"@esbuild/linux-mips64el@0.25.5":
|
||||
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/linux-ppc64@0.25.5":
|
||||
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/linux-riscv64@0.25.5":
|
||||
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/linux-s390x@0.25.5":
|
||||
version "0.25.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz#5669af81327a398a336d7e40e320b5bbd6e6e72d"
|
||||
integrity sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==
|
||||
|
||||
"@esbuild/linux-x64@0.25.5":
|
||||
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/netbsd-arm64@0.25.5":
|
||||
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/netbsd-x64@0.25.5":
|
||||
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/openbsd-arm64@0.25.5":
|
||||
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/openbsd-x64@0.25.5":
|
||||
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/sunos-x64@0.25.5":
|
||||
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/win32-arm64@0.25.5":
|
||||
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/win32-ia32@0.25.5":
|
||||
version "0.25.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz#bab6288005482f9ed2adb9ded7e88eba9a62cc0d"
|
||||
integrity sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==
|
||||
|
||||
"@esbuild/win32-x64@0.25.5":
|
||||
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/api-extractor-model@7.30.6":
|
||||
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/tsdoc@0.15.1", "@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/rollup-android-arm-eabi@4.41.1":
|
||||
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/rollup-android-arm64@4.41.1":
|
||||
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/rollup-darwin-arm64@4.41.1":
|
||||
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/rollup-darwin-x64@4.41.1":
|
||||
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/rollup-freebsd-arm64@4.41.1":
|
||||
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/rollup-freebsd-x64@4.41.1":
|
||||
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/rollup-linux-arm-gnueabihf@4.41.1":
|
||||
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/rollup-linux-arm-musleabihf@4.41.1":
|
||||
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/rollup-linux-arm64-gnu@4.41.1":
|
||||
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/rollup-linux-arm64-musl@4.41.1":
|
||||
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/rollup-linux-loongarch64-gnu@4.41.1":
|
||||
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/rollup-linux-powerpc64le-gnu@4.41.1":
|
||||
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/rollup-linux-riscv64-gnu@4.41.1":
|
||||
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/rollup-linux-riscv64-musl@4.41.1":
|
||||
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/rollup-linux-s390x-gnu@4.41.1":
|
||||
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/rollup-linux-x64-gnu@4.41.1":
|
||||
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/rollup-linux-x64-musl@4.41.1":
|
||||
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/rollup-win32-arm64-msvc@4.41.1":
|
||||
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/rollup-win32-ia32-msvc@4.41.1":
|
||||
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/rollup-win32-x64-msvc@4.41.1":
|
||||
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/node-core-library@5.13.1":
|
||||
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/rig-package@0.5.3":
|
||||
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/terminal@0.15.3":
|
||||
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/ts-command-line@5.0.1":
|
||||
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/argparse@1.0.38":
|
||||
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/estree@1.0.7", "@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/node@22.12.0":
|
||||
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/language-core@2.4.14", "@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/source-map@2.4.14":
|
||||
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/compiler-core@3.5.16":
|
||||
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/language-core@2.2.0":
|
||||
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/shared@3.5.16", "@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==
|
||||
|
||||
concat-map@0.0.1:
|
||||
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"
|
||||
|
||||
typescript@5.4.2:
|
||||
version "5.4.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372"
|
||||
integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==
|
||||
|
||||
typescript@5.8.2:
|
||||
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"
|
||||
|
||||
vite-plugin-dts@4.5.4:
|
||||
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"
|
||||
|
||||
vite@6.3.5:
|
||||
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==
|
||||
@@ -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
|
||||
|
||||
Vendored
+2
@@ -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 {
|
||||
|
||||
+5
-2
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 } ]) => {
|
||||
|
||||
+4
-1
@@ -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;
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
@@ -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> :
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -63,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',
|
||||
|
||||
|
||||
@@ -66,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,
|
||||
|
||||
|
||||
@@ -63,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%',
|
||||
|
||||
|
||||
@@ -61,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',
|
||||
|
||||
|
||||
+14
-3
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 }>;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 || '');
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ function generateCspPolicy() {
|
||||
descriptors.marketplace(),
|
||||
descriptors.mixpanel(),
|
||||
descriptors.monaco(),
|
||||
descriptors.multichain(),
|
||||
descriptors.rollbar(),
|
||||
descriptors.safe(),
|
||||
descriptors.usernameApi(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
@@ -411,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 {
|
||||
|
||||
Vendored
+4
@@ -38,6 +38,10 @@ 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">
|
||||
|
||||
@@ -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
-1
@@ -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",
|
||||
|
||||
+4
-1
@@ -8,6 +8,7 @@ import React from 'react';
|
||||
import type { NextPageWithLayout } from 'nextjs/types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getSocketUrl from 'lib/api/getSocketUrl';
|
||||
import useQueryClientConfig from 'lib/api/useQueryClientConfig';
|
||||
import { AppContextProvider } from 'lib/contexts/app';
|
||||
import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
|
||||
@@ -72,6 +73,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
);
|
||||
})();
|
||||
|
||||
const socketUrl = !config.features.opSuperchain.isEnabled ? getSocketUrl() : undefined;
|
||||
|
||||
return (
|
||||
<ChakraProvider>
|
||||
<RollbarProvider config={ rollbarConfig }>
|
||||
@@ -84,7 +87,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
<QueryClientProvider client={ queryClient }>
|
||||
<GrowthBookProvider growthbook={ growthBook }>
|
||||
<ScrollDirectionProvider>
|
||||
<SocketProvider url={ `${ config.apis.general.socketEndpoint }${ config.apis.general.basePath ?? '' }/socket/v2` }>
|
||||
<SocketProvider url={ socketUrl }>
|
||||
<RewardsContextProvider>
|
||||
<MarketplaceContextProvider>
|
||||
<SettingsContextProvider>
|
||||
|
||||
@@ -44,6 +44,12 @@ class MyDocument extends Document {
|
||||
|
||||
{ /* eslint-disable-next-line @next/next/no-sync-scripts */ }
|
||||
<script src="/assets/envs.js"/>
|
||||
{ config.features.opSuperchain.isEnabled && (
|
||||
<>
|
||||
{ /* eslint-disable-next-line @next/next/no-sync-scripts */ }
|
||||
<script src="/assets/multichain/config.js"/>
|
||||
</>
|
||||
) }
|
||||
|
||||
{ /* FAVICON */ }
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon/favicon-16x16.png"/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import fetchApi from 'nextjs/utils/fetchApi';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import AddressOpSuperchain from 'ui/optimismSuperchain/address/AddressOpSuperchain';
|
||||
import Address from 'ui/pages/Address';
|
||||
|
||||
const pathname: Route['pathname'] = '/address/[hash]';
|
||||
@@ -17,7 +18,7 @@ const pathname: Route['pathname'] = '/address/[hash]';
|
||||
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
|
||||
return (
|
||||
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
|
||||
<Address/>
|
||||
{ config.features.opSuperchain.isEnabled ? <AddressOpSuperchain/> : <Address/> }
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
@@ -27,7 +28,7 @@ export default Page;
|
||||
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => {
|
||||
const baseResponse = await gSSP.base<typeof pathname>(ctx);
|
||||
|
||||
if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
|
||||
if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse && !config.features.opSuperchain.isEnabled) {
|
||||
const botInfo = detectBotRequest(ctx.req);
|
||||
|
||||
if (botInfo?.type === 'social_preview') {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { NextPage } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
|
||||
const AccountsLabelSearch = dynamic(() => import('ui/pages/AccountsLabelSearch'), { ssr: false });
|
||||
|
||||
const Page: NextPage = () => {
|
||||
return (
|
||||
<PageNextJs pathname="/chain/[chain-slug]/accounts/label/[slug]">
|
||||
<MultichainProvider>
|
||||
<AccountsLabelSearch/>
|
||||
</MultichainProvider>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export { opSuperchainAccountsLabelSearch as getServerSideProps } from 'nextjs/getServerSideProps';
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { NextPage } from 'next';
|
||||
import React from 'react';
|
||||
|
||||
import type { Route } from 'nextjs-routes';
|
||||
import type { Props } from 'nextjs/getServerSideProps';
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import multichainConfig from 'configs/multichain';
|
||||
import getSocketUrl from 'lib/api/getSocketUrl';
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import Address from 'ui/pages/Address';
|
||||
|
||||
const pathname: Route['pathname'] = '/chain/[chain-slug]/address/[hash]';
|
||||
|
||||
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
|
||||
const chainSlug = props.query?.['chain-slug'];
|
||||
const chainData = multichainConfig()?.chains.find(chain => chain.slug === chainSlug);
|
||||
|
||||
return (
|
||||
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
|
||||
<SocketProvider url={ getSocketUrl(chainData?.config) }>
|
||||
<MultichainProvider>
|
||||
<Address/>
|
||||
</MultichainProvider>
|
||||
</SocketProvider>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export { opSuperchain as getServerSideProps } from 'nextjs/getServerSideProps';
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { NextPage } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
import type { Props } from 'nextjs/getServerSideProps';
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
|
||||
const Block = dynamic(() => import('ui/pages/Block'), { ssr: false });
|
||||
|
||||
const Page: NextPage<Props> = (props: Props) => {
|
||||
return (
|
||||
<PageNextJs pathname="/chain/[chain-slug]/block/[height_or_hash]" query={ props.query }>
|
||||
<MultichainProvider>
|
||||
<Block/>
|
||||
</MultichainProvider>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export { opSuperchain as getServerSideProps } from 'nextjs/getServerSideProps';
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { NextPage } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
import type { Props } from 'nextjs/getServerSideProps';
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
|
||||
const Transaction = dynamic(() => import('ui/pages/Transaction'), { ssr: false });
|
||||
|
||||
const Page: NextPage<Props> = (props: Props) => {
|
||||
return (
|
||||
<PageNextJs pathname="/chain/[chain-slug]/tx/[hash]" query={ props.query }>
|
||||
<MultichainProvider>
|
||||
<Transaction/>
|
||||
</MultichainProvider>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export { opSuperchain as getServerSideProps } from 'nextjs/getServerSideProps';
|
||||
+3
-1
@@ -4,13 +4,15 @@ import type { NextPageWithLayout } from 'nextjs/types';
|
||||
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import config from 'configs/app';
|
||||
import HomeOpSuperchain from 'ui/optimismSuperchain/home/HomeOpSuperchain';
|
||||
import Home from 'ui/pages/Home';
|
||||
import LayoutHome from 'ui/shared/layout/LayoutHome';
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
return (
|
||||
<PageNextJs pathname="/">
|
||||
<Home/>
|
||||
{ config.features.opSuperchain.isEnabled ? <HomeOpSuperchain/> : <Home/> }
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ const defaultMarketplaceContext = {
|
||||
setIsAutoConnectDisabled: () => {},
|
||||
};
|
||||
|
||||
const wagmiConfig = createConfig({
|
||||
const wagmiConfig = currentChain ? createConfig({
|
||||
chains: [ currentChain ],
|
||||
connectors: [
|
||||
mock({
|
||||
@@ -59,7 +59,7 @@ const wagmiConfig = createConfig({
|
||||
transports: {
|
||||
[currentChain.id]: http(),
|
||||
},
|
||||
});
|
||||
}) : undefined;
|
||||
|
||||
const TestApp = ({ children, withSocket, appContext = defaultAppContext, marketplaceContext = defaultMarketplaceContext }: Props) => {
|
||||
const [ queryClient ] = React.useState(() => new QueryClient({
|
||||
@@ -79,7 +79,7 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext, marketp
|
||||
<MarketplaceContext.Provider value={ marketplaceContext }>
|
||||
<SettingsContextProvider>
|
||||
<GrowthBookProvider>
|
||||
<WagmiProvider config={ wagmiConfig }>
|
||||
<WagmiProvider config={ wagmiConfig! }>
|
||||
<RewardsContextProvider>
|
||||
{ children }
|
||||
</RewardsContextProvider>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,6 @@
|
||||
import config from 'configs/app';
|
||||
import * as multichainConfig from 'configs/multichain/config.nodejs';
|
||||
|
||||
(async() => {
|
||||
config.features.opSuperchain.isEnabled && await multichainConfig.load();
|
||||
})();
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as multichain from '@blockscout/multichain-aggregator-types';
|
||||
|
||||
import { ADDRESS_HASH } from './addressParams';
|
||||
import { TX_HASH } from './tx';
|
||||
|
||||
export const INTEROP_MESSAGE: multichain.InteropMessage = {
|
||||
sender: {
|
||||
hash: ADDRESS_HASH,
|
||||
},
|
||||
target: {
|
||||
hash: ADDRESS_HASH,
|
||||
},
|
||||
nonce: 4261,
|
||||
init_chain_id: '420120000',
|
||||
init_transaction_hash: TX_HASH,
|
||||
timestamp: '2025-06-03T10:43:58.000Z',
|
||||
relay_chain_id: '420120001',
|
||||
relay_transaction_hash: TX_HASH,
|
||||
payload: '0x4f0edcc90000000000000000000000004',
|
||||
message_type: 'coin_transfer',
|
||||
method: 'sendERC20',
|
||||
status: multichain.InteropMessage_Status.PENDING,
|
||||
};
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# remove previous assets
|
||||
rm -rf ./public/assets/configs
|
||||
rm -rf ./public/assets/multichain
|
||||
rm -rf ./public/assets/envs.js
|
||||
|
||||
docker build --progress=plain --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./
|
||||
@@ -14,11 +14,23 @@ if [ ! -f "$config_file" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# remove previous assets
|
||||
rm -rf ./public/assets/configs
|
||||
rm -rf ./public/assets/multichain
|
||||
rm -rf ./public/assets/envs.js
|
||||
|
||||
# download assets for the running instance
|
||||
dotenv \
|
||||
-e $config_file \
|
||||
-- bash -c './deploy/scripts/download_assets.sh ./public/assets/configs'
|
||||
|
||||
# generate multichain config (adjust condition accordingly)
|
||||
if [[ "$preset_name" == "optimism_superchain" ]]; then
|
||||
dotenv \
|
||||
-e $config_file \
|
||||
-- bash -c 'cd deploy/tools/multichain-config-generator && yarn install --silent && yarn build && yarn generate'
|
||||
fi
|
||||
|
||||
source ./deploy/scripts/build_sprite.sh
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# remove previous assets
|
||||
rm -rf ./public/assets/configs
|
||||
rm -rf ./public/assets/multichain
|
||||
rm -rf ./public/assets/envs.js
|
||||
|
||||
# download assets for the running instance
|
||||
dotenv \
|
||||
-e .env.development.local \
|
||||
|
||||
+1
-1
@@ -18,5 +18,5 @@
|
||||
"types": ["node", "jest"],
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.node.ts", "**/*.tsx", "**/*.pw.tsx", "decs.d.ts", "global.d.ts"],
|
||||
"exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator", "./deploy/tools/favicon-generator", "./toolkit/package"],
|
||||
"exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator", "./deploy/tools/favicon-generator", "./deploy/tools/multichain-config-generator", "./toolkit/package"],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type config from 'configs/app';
|
||||
|
||||
export interface ChainConfig {
|
||||
slug: string;
|
||||
config: typeof config;
|
||||
}
|
||||
|
||||
export interface MultichainConfig {
|
||||
chains: Array<ChainConfig>;
|
||||
}
|
||||
@@ -1,28 +1,16 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressFromToFilter } from 'types/api/address';
|
||||
import { AddressFromToFilterValues } from 'types/api/address';
|
||||
import type { TransactionsSortingField, TransactionsSortingValue, TransactionsSorting } from 'types/api/transaction';
|
||||
|
||||
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import useIsMounted from 'lib/hooks/useIsMounted';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { TX } from 'stubs/tx';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
|
||||
import Pagination from 'ui/shared/pagination/Pagination';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
|
||||
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
|
||||
import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting';
|
||||
import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
|
||||
|
||||
import AddressCsvExportLink from './AddressCsvExportLink';
|
||||
import AddressTxsFilter from './AddressTxsFilter';
|
||||
|
||||
const getFilterValue = (getFilterValueFromQuery<AddressFromToFilter>).bind(null, AddressFromToFilterValues);
|
||||
import useAddressTxsQuery from './useAddressTxsQuery';
|
||||
|
||||
type Props = {
|
||||
shouldRender?: boolean;
|
||||
@@ -32,37 +20,14 @@ type Props = {
|
||||
const AddressTxs = ({ shouldRender = true, isQueryEnabled = true }: Props) => {
|
||||
const router = useRouter();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const [ sort, setSort ] = React.useState<TransactionsSortingValue>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS) || 'default');
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const currentAddress = getQueryParamString(router.query.hash);
|
||||
|
||||
const initialFilterValue = getFilterValue(router.query.filter);
|
||||
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(initialFilterValue);
|
||||
|
||||
const addressTxsQuery = useQueryWithPages({
|
||||
resourceName: 'general:address_txs',
|
||||
pathParams: { hash: currentAddress },
|
||||
filters: { filter: filterValue },
|
||||
sorting: getSortParamsFromValue<TransactionsSortingValue, TransactionsSortingField, TransactionsSorting['order']>(sort),
|
||||
options: {
|
||||
enabled: isQueryEnabled,
|
||||
placeholderData: generateListStub<'general:address_txs'>(TX, 50, { next_page_params: {
|
||||
block_number: 9005713,
|
||||
index: 5,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
const { query, filterValue, initialFilterValue, onFilterChange, sort, setSort } = useAddressTxsQuery({
|
||||
addressHash: currentAddress,
|
||||
enabled: isQueryEnabled,
|
||||
});
|
||||
|
||||
const handleFilterChange = React.useCallback((val: string | Array<string>) => {
|
||||
|
||||
const newVal = getFilterValue(val);
|
||||
setFilterValue(newVal);
|
||||
addressTxsQuery.onFilterChange({ filter: newVal });
|
||||
}, [ addressTxsQuery ]);
|
||||
|
||||
if (!isMounted || !shouldRender) {
|
||||
return null;
|
||||
}
|
||||
@@ -70,9 +35,9 @@ const AddressTxs = ({ shouldRender = true, isQueryEnabled = true }: Props) => {
|
||||
const filter = (
|
||||
<AddressTxsFilter
|
||||
initialValue={ initialFilterValue }
|
||||
onFilterChange={ handleFilterChange }
|
||||
onFilterChange={ onFilterChange }
|
||||
hasActiveFilter={ Boolean(filterValue) }
|
||||
isLoading={ addressTxsQuery.pagination.isLoading }
|
||||
isLoading={ query.pagination.isLoading }
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -81,7 +46,7 @@ const AddressTxs = ({ shouldRender = true, isQueryEnabled = true }: Props) => {
|
||||
address={ currentAddress }
|
||||
params={{ type: 'transactions', filterType: 'address', filterValue }}
|
||||
ml="auto"
|
||||
isLoading={ addressTxsQuery.pagination.isLoading }
|
||||
isLoading={ query.pagination.isLoading }
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -91,13 +56,13 @@ const AddressTxs = ({ shouldRender = true, isQueryEnabled = true }: Props) => {
|
||||
<ActionBar>
|
||||
{ filter }
|
||||
{ currentAddress && csvExportLink }
|
||||
<Pagination { ...addressTxsQuery.pagination } ml={ 8 }/>
|
||||
<Pagination { ...query.pagination } ml={ 8 }/>
|
||||
</ActionBar>
|
||||
) }
|
||||
<TxsWithAPISorting
|
||||
filter={ filter }
|
||||
filterValue={ filterValue }
|
||||
query={ addressTxsQuery }
|
||||
query={ query }
|
||||
currentAddress={ typeof currentAddress === 'string' ? currentAddress : undefined }
|
||||
enableTimeIncrement
|
||||
socketType="address_txs"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { usePublicClient } from 'wagmi';
|
||||
import type { FormSubmitResult, MethodCallStrategy, SmartContractMethod } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
|
||||
import { getNativeCoinValue } from './utils';
|
||||
@@ -17,7 +18,9 @@ interface Params {
|
||||
}
|
||||
|
||||
export default function useCallMethodPublicClient(): (params: Params) => Promise<FormSubmitResult> {
|
||||
const publicClient = usePublicClient({ chainId: Number(config.chain.id) });
|
||||
const multichainContext = useMultichainContext();
|
||||
const chainId = Number((multichainContext?.chain.config ?? config).chain.id);
|
||||
const publicClient = usePublicClient({ chainId });
|
||||
const { address: account } = useAccount();
|
||||
|
||||
return React.useCallback(async({ args, item, addressHash, strategy }) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAccount, useWalletClient, useSwitchChain } from 'wagmi';
|
||||
import type { FormSubmitResult, SmartContractMethod } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import useRewardsActivity from 'lib/hooks/useRewardsActivity';
|
||||
|
||||
import { getNativeCoinValue } from './utils';
|
||||
@@ -16,7 +17,10 @@ interface Params {
|
||||
}
|
||||
|
||||
export default function useCallMethodWalletClient(): (params: Params) => Promise<FormSubmitResult> {
|
||||
const { data: walletClient } = useWalletClient();
|
||||
const multichainContext = useMultichainContext();
|
||||
const chainConfig = (multichainContext?.chain.config ?? config).chain;
|
||||
|
||||
const { data: walletClient } = useWalletClient({ chainId: Number(chainConfig.id) });
|
||||
const { isConnected, chainId, address: account } = useAccount();
|
||||
const { switchChainAsync } = useSwitchChain();
|
||||
const { trackTransaction, trackTransactionConfirm } = useRewardsActivity();
|
||||
@@ -30,8 +34,8 @@ export default function useCallMethodWalletClient(): (params: Params) => Promise
|
||||
throw new Error('Wallet Client is not defined');
|
||||
}
|
||||
|
||||
if (chainId && String(chainId) !== config.chain.id) {
|
||||
await switchChainAsync({ chainId: Number(config.chain.id) });
|
||||
if (chainId && String(chainId) !== chainConfig.id) {
|
||||
await switchChainAsync({ chainId: Number(chainConfig.id) });
|
||||
}
|
||||
|
||||
const address = getAddress(addressHash);
|
||||
@@ -81,5 +85,5 @@ export default function useCallMethodWalletClient(): (params: Params) => Promise
|
||||
}
|
||||
|
||||
return { source: 'wallet_client', data: { hash } };
|
||||
}, [ chainId, isConnected, switchChainAsync, walletClient, account, trackTransaction, trackTransactionConfirm ]);
|
||||
}, [ chainId, chainConfig, isConnected, switchChainAsync, walletClient, account, trackTransaction, trackTransactionConfirm ]);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import React from 'react';
|
||||
|
||||
import type { AddressCounters } from 'types/api/address';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
import { route } from 'nextjs/routes';
|
||||
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
|
||||
@@ -25,6 +26,7 @@ const PROP_TO_TAB = {
|
||||
};
|
||||
|
||||
const AddressCounterItem = ({ prop, query, address, isAddressQueryLoading, isDegradedData }: Props) => {
|
||||
const multichainContext = useMultichainContext();
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
@@ -56,7 +58,7 @@ const AddressCounterItem = ({ prop, query, address, isAddressQueryLoading, isDeg
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }) }
|
||||
href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }, multichainContext) }
|
||||
scroll={ false }
|
||||
onClick={ handleClick }
|
||||
>
|
||||
|
||||
@@ -6,9 +6,10 @@ import React from 'react';
|
||||
|
||||
import type { Address } from 'types/api/address';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
import { route } from 'nextjs/routes';
|
||||
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
@@ -26,14 +27,19 @@ const TokenSelect = () => {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
const queryClient = useQueryClient();
|
||||
const multichainContext = useMultichainContext();
|
||||
|
||||
const addressHash = getQueryParamString(router.query.hash);
|
||||
const addressResourceKey = getResourceKey('general:address', { pathParams: { hash: addressHash } });
|
||||
const addressResourceKey = getResourceKey('general:address', { pathParams: { hash: addressHash }, chainSlug: multichainContext?.chain?.slug });
|
||||
|
||||
const addressQueryData = queryClient.getQueryData<Address>(addressResourceKey);
|
||||
|
||||
const { data, isError, isPending } = useFetchTokens({ hash: addressQueryData?.hash });
|
||||
const tokensResourceKey = getResourceKey('general:address_tokens', { pathParams: { hash: addressQueryData?.hash }, queryParams: { type: 'ERC-20' } });
|
||||
const tokensResourceKey = getResourceKey('general:address_tokens', {
|
||||
pathParams: { hash: addressQueryData?.hash },
|
||||
queryParams: { type: 'ERC-20' },
|
||||
chainSlug: multichainContext?.chain?.slug,
|
||||
});
|
||||
const tokensIsFetching = useIsFetching({ queryKey: tokensResourceKey });
|
||||
|
||||
const handleIconButtonClick = React.useCallback(() => {
|
||||
@@ -63,7 +69,7 @@ const TokenSelect = () => {
|
||||
}
|
||||
<Tooltip content="Show all tokens">
|
||||
<Link
|
||||
href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'tokens' } }) }
|
||||
href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'tokens' } }, multichainContext) }
|
||||
asChild
|
||||
scroll={ false }
|
||||
>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressFromToFilter } from 'types/api/address';
|
||||
import { AddressFromToFilterValues } from 'types/api/address';
|
||||
import type { TransactionsSorting, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
|
||||
|
||||
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
|
||||
import { TX } from 'stubs/tx';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
|
||||
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
|
||||
import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
|
||||
|
||||
const getFilterValue = (getFilterValueFromQuery<AddressFromToFilter>).bind(null, AddressFromToFilterValues);
|
||||
|
||||
interface Props {
|
||||
addressHash: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export default function useAddressTxsQuery({ addressHash, enabled }: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
const [ sort, setSort ] = React.useState<TransactionsSortingValue>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS) || 'default');
|
||||
|
||||
const initialFilterValue = getFilterValue(router.query.filter);
|
||||
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(initialFilterValue);
|
||||
|
||||
const query = useQueryWithPages({
|
||||
resourceName: 'general:address_txs',
|
||||
pathParams: { hash: addressHash },
|
||||
filters: { filter: filterValue },
|
||||
sorting: getSortParamsFromValue<TransactionsSortingValue, TransactionsSortingField, TransactionsSorting['order']>(sort),
|
||||
options: {
|
||||
enabled: enabled,
|
||||
placeholderData: generateListStub<'general:address_txs'>(TX, 50, { next_page_params: {
|
||||
block_number: 9005713,
|
||||
index: 5,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
});
|
||||
|
||||
const onFilterChange = React.useCallback((val: string | Array<string>) => {
|
||||
const newVal = getFilterValue(val);
|
||||
setFilterValue(newVal);
|
||||
query.onFilterChange({ filter: newVal });
|
||||
}, [ query ]);
|
||||
|
||||
return React.useMemo(() => ({
|
||||
query,
|
||||
filterValue,
|
||||
initialFilterValue,
|
||||
onFilterChange,
|
||||
sort,
|
||||
setSort,
|
||||
}), [ query, filterValue, initialFilterValue, onFilterChange, sort ]);
|
||||
}
|
||||
@@ -6,10 +6,11 @@ import React from 'react';
|
||||
|
||||
import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
import { route, routeParams } from 'nextjs/routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getBlockReward from 'lib/block/getBlockReward';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
|
||||
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
|
||||
import * as arbitrum from 'lib/rollups/arbitrum';
|
||||
@@ -53,6 +54,7 @@ const rollupFeature = config.features.rollup;
|
||||
const BlockDetails = ({ query }: Props) => {
|
||||
const router = useRouter();
|
||||
const heightOrHash = getQueryParamString(router.query.height_or_hash);
|
||||
const multichainContext = useMultichainContext();
|
||||
|
||||
const { data, isPlaceholderData } = query;
|
||||
|
||||
@@ -64,8 +66,8 @@ const BlockDetails = ({ query }: Props) => {
|
||||
const increment = direction === 'next' ? +1 : -1;
|
||||
const nextId = String(data.height + increment);
|
||||
|
||||
router.push({ pathname: '/block/[height_or_hash]', query: { height_or_hash: nextId } }, undefined);
|
||||
}, [ data, router ]);
|
||||
router.push(routeParams({ pathname: '/block/[height_or_hash]', query: { height_or_hash: nextId } }, multichainContext), undefined);
|
||||
}, [ data, multichainContext, router ]);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
@@ -113,7 +115,7 @@ const BlockDetails = ({ query }: Props) => {
|
||||
|
||||
const txsNum = (() => {
|
||||
const blockTxsNum = (
|
||||
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'txs' } }) }>
|
||||
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'txs' } }, multichainContext) }>
|
||||
{ data.transactions_count } txn{ data.transactions_count === 1 ? '' : 's' }
|
||||
</Link>
|
||||
);
|
||||
@@ -121,7 +123,7 @@ const BlockDetails = ({ query }: Props) => {
|
||||
const blockBlobTxsNum = (config.features.dataAvailability.isEnabled && data.blob_transaction_count) ? (
|
||||
<>
|
||||
<span> including </span>
|
||||
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'blob_txs' } }) }>
|
||||
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'blob_txs' } }, multichainContext) }>
|
||||
{ data.blob_transaction_count } blob txn{ data.blob_transaction_count === 1 ? '' : 's' }
|
||||
</Link>
|
||||
</>
|
||||
@@ -266,7 +268,7 @@ const BlockDetails = ({ query }: Props) => {
|
||||
</DetailedInfo.ItemLabel>
|
||||
<DetailedInfo.ItemValue>
|
||||
<Skeleton loading={ isPlaceholderData }>
|
||||
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'withdrawals' } }) }>
|
||||
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'withdrawals' } }, multichainContext) }>
|
||||
{ data.withdrawals_count } withdrawal{ data.withdrawals_count === 1 ? '' : 's' }
|
||||
</Link>
|
||||
</Skeleton>
|
||||
@@ -666,7 +668,7 @@ const BlockDetails = ({ query }: Props) => {
|
||||
</DetailedInfo.ItemLabel>
|
||||
<DetailedInfo.ItemValue flexWrap="nowrap">
|
||||
<Link
|
||||
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.height - 1) } }) }
|
||||
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.height - 1) } }, multichainContext) }
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
|
||||
@@ -162,6 +162,8 @@ export default function useBlockTxsQuery({ heightOrHash, blockQuery, tab }: Para
|
||||
pagination: emptyPagination,
|
||||
onFilterChange: () => {},
|
||||
onSortingChange: () => {},
|
||||
chainValue: undefined,
|
||||
onChainValueChange: () => {},
|
||||
};
|
||||
|
||||
const query = isRpcQuery ? rpcQueryWithPages : apiQuery;
|
||||
|
||||
@@ -129,6 +129,8 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab
|
||||
pagination: emptyPagination,
|
||||
onFilterChange: () => {},
|
||||
onSortingChange: () => {},
|
||||
chainValue: undefined,
|
||||
onChainValueChange: () => {},
|
||||
};
|
||||
|
||||
const query = isRpcQuery ? rpcQueryWithPages : apiQuery;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
|
||||
|
||||
import getCheckedSummedAddress from 'lib/address/getCheckedSummedAddress';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
|
||||
import AddressQrCode from 'ui/address/details/AddressQrCode';
|
||||
import TextAd from 'ui/shared/ad/TextAd';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
|
||||
import AddressOpSuperchainTxs, { ADDRESS_OP_SUPERCHAIN_TXS_TAB_IDS } from './AddressOpSuperchainTxs';
|
||||
|
||||
const AddressOpSuperchain = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const hash = getQueryParamString(router.query.hash);
|
||||
|
||||
const isLoading = false;
|
||||
|
||||
const addressQuery = {
|
||||
data: {
|
||||
hash: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const checkSummedHash = React.useMemo(() => addressQuery.data?.hash ?? getCheckedSummedAddress(hash), [ hash, addressQuery.data?.hash ]);
|
||||
|
||||
const tabs: Array<TabItemRegular> = React.useMemo(() => {
|
||||
return [
|
||||
{
|
||||
id: 'index',
|
||||
title: 'Details',
|
||||
component: <div>Coming soon 🔜</div>,
|
||||
},
|
||||
{
|
||||
id: 'txs',
|
||||
title: 'Transactions',
|
||||
component: <AddressOpSuperchainTxs/>,
|
||||
subTabs: ADDRESS_OP_SUPERCHAIN_TXS_TAB_IDS,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
const titleSecondRow = (
|
||||
<Flex alignItems="center" w="100%" columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
|
||||
<AddressEntity
|
||||
address={{
|
||||
...addressQuery.data,
|
||||
hash: checkSummedHash,
|
||||
name: '',
|
||||
ens_domain_name: '',
|
||||
implementations: null,
|
||||
}}
|
||||
isLoading={ isLoading }
|
||||
variant="subheading"
|
||||
noLink
|
||||
/>
|
||||
<AddressQrCode hash={ checkSummedHash } isLoading={ isLoading }/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
<PageTitle
|
||||
title="Address details"
|
||||
isLoading={ isLoading }
|
||||
secondRow={ titleSecondRow }
|
||||
/>
|
||||
<RoutedTabs tabs={ tabs } isLoading={ isLoading }/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddressOpSuperchain);
|
||||
@@ -0,0 +1,115 @@
|
||||
import { HStack } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
|
||||
|
||||
import multichainConfig from 'configs/multichain';
|
||||
import getSocketUrl from 'lib/api/getSocketUrl';
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
|
||||
// import AddressCsvExportLink from 'ui/address/AddressCsvExportLink';
|
||||
import AddressTxsFilter from 'ui/address/AddressTxsFilter';
|
||||
import useAddressTxsQuery from 'ui/address/useAddressTxsQuery';
|
||||
import ChainSelect from 'ui/shared/multichain/ChainSelect';
|
||||
import Pagination from 'ui/shared/pagination/Pagination';
|
||||
import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting';
|
||||
|
||||
export const ADDRESS_OP_SUPERCHAIN_TXS_TAB_IDS = [ 'cross_chain_txs', 'local_txs' ];
|
||||
const TAB_LIST_PROPS = {
|
||||
marginBottom: 0,
|
||||
pt: 6,
|
||||
pb: 3,
|
||||
marginTop: -6,
|
||||
};
|
||||
const ACTION_BAR_HEIGHT_DESKTOP = 68;
|
||||
|
||||
const AddressOpSuperchainTxs = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const hash = getQueryParamString(router.query.hash);
|
||||
const tab = getQueryParamString(router.query.tab);
|
||||
|
||||
const txsQueryLocal = useAddressTxsQuery({
|
||||
addressHash: hash,
|
||||
enabled: tab === 'local_txs',
|
||||
});
|
||||
|
||||
const txsLocalFilter = tab === 'local_txs' ? (
|
||||
<AddressTxsFilter
|
||||
initialValue={ txsQueryLocal.filterValue }
|
||||
onFilterChange={ txsQueryLocal.onFilterChange }
|
||||
hasActiveFilter={ Boolean(txsQueryLocal.filterValue) }
|
||||
isLoading={ txsQueryLocal.query.pagination.isLoading }
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const rightSlot = tab === 'local_txs' ? (
|
||||
<>
|
||||
<HStack gap={ 2 }>
|
||||
{ txsLocalFilter }
|
||||
<ChainSelect
|
||||
loading={ txsQueryLocal.query.pagination.isLoading }
|
||||
value={ txsQueryLocal.query.chainValue }
|
||||
onValueChange={ txsQueryLocal.query.onChainValueChange }
|
||||
/>
|
||||
</HStack>
|
||||
<HStack gap={ 6 }>
|
||||
{ /* <AddressCsvExportLink
|
||||
address={ hash }
|
||||
params={{ type: 'transactions', filterType: 'address', filterValue: txsQueryLocal.filterValue }}
|
||||
ml="auto"
|
||||
isLoading={ txsQueryLocal.query.pagination.isLoading }
|
||||
/> */ }
|
||||
<Pagination { ...txsQueryLocal.query.pagination } ml={ 8 }/>
|
||||
</HStack>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const chainData = multichainConfig()?.chains.find(chain => chain.slug === txsQueryLocal.query.chainValue?.[0]);
|
||||
|
||||
const tabs: Array<TabItemRegular> = [
|
||||
{
|
||||
id: 'cross_chain_txs',
|
||||
title: 'Cross-chain',
|
||||
component: <div>Coming soon 🔜</div>,
|
||||
},
|
||||
{
|
||||
id: 'local_txs',
|
||||
title: 'Local',
|
||||
component: (
|
||||
<SocketProvider url={ getSocketUrl(chainData?.config) }>
|
||||
<MultichainProvider chainSlug={ txsQueryLocal.query.chainValue?.[0] }>
|
||||
<TxsWithAPISorting
|
||||
filter={ txsLocalFilter }
|
||||
filterValue={ txsQueryLocal.filterValue }
|
||||
query={ txsQueryLocal.query }
|
||||
currentAddress={ hash }
|
||||
enableTimeIncrement
|
||||
socketType="address_txs"
|
||||
top={ ACTION_BAR_HEIGHT_DESKTOP }
|
||||
sorting={ txsQueryLocal.sort }
|
||||
setSort={ txsQueryLocal.setSort }
|
||||
/>
|
||||
</MultichainProvider>
|
||||
</SocketProvider>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<RoutedTabs
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
tabs={ tabs }
|
||||
rightSlot={ rightSlot }
|
||||
rightSlotProps={{ display: 'flex', justifyContent: 'space-between', ml: 8, widthAllocation: 'available' }}
|
||||
listProps={ TAB_LIST_PROPS }
|
||||
stickyEnabled
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddressOpSuperchainTxs);
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as multichain from '@blockscout/multichain-aggregator-types';
|
||||
|
||||
import type { BadgeProps } from 'toolkit/chakra/badge';
|
||||
import StatusTag from 'ui/shared/statusTag/StatusTag';
|
||||
|
||||
interface Props extends BadgeProps {
|
||||
status: multichain.InteropMessage_Status;
|
||||
}
|
||||
|
||||
const CrossChainTxStatusTag = ({ status: statusProp, ...rest }: Props) => {
|
||||
|
||||
const { status, text } = (() => {
|
||||
switch (statusProp) {
|
||||
case multichain.InteropMessage_Status.SUCCESS:
|
||||
return { status: 'ok' as const, text: 'Relayed' };
|
||||
case multichain.InteropMessage_Status.FAILED:
|
||||
return { status: 'error' as const, text: 'Failed' };
|
||||
case multichain.InteropMessage_Status.PENDING:
|
||||
return { status: 'pending' as const, text: 'Sent' };
|
||||
default:
|
||||
return { status: undefined, text: undefined };
|
||||
}
|
||||
})();
|
||||
|
||||
if (!status || !text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <StatusTag type={ status } text={ text } { ...rest }/>;
|
||||
};
|
||||
|
||||
export default React.memo(CrossChainTxStatusTag);
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import type * as multichain from '@blockscout/multichain-aggregator-types';
|
||||
import type { TxsSocketType } from 'ui/txs/socket/types';
|
||||
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
|
||||
import TimeFormatToggle from 'ui/shared/time/TimeFormatToggle';
|
||||
import TxsSocketNotice from 'ui/txs/socket/TxsSocketNotice';
|
||||
|
||||
import CrossChainTxsTableItem from './CrossChainTxsTableItem';
|
||||
|
||||
interface Props {
|
||||
items: Array<multichain.InteropMessage>;
|
||||
isLoading: boolean;
|
||||
socketType?: TxsSocketType;
|
||||
}
|
||||
|
||||
const CrossChainTxsTable = ({ items, isLoading, socketType }: Props) => {
|
||||
|
||||
return (
|
||||
<AddressHighlightProvider>
|
||||
<TableRoot minW="1150px">
|
||||
<TableHeaderSticky top={ 68 }>
|
||||
<TableRow>
|
||||
<TableColumnHeader width="52px"/>
|
||||
<TableColumnHeader w="180px">
|
||||
Message
|
||||
<TimeFormatToggle/>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader w="130px">Type</TableColumnHeader>
|
||||
<TableColumnHeader w="130px">Method</TableColumnHeader>
|
||||
<TableColumnHeader w="25%">Source tx</TableColumnHeader>
|
||||
<TableColumnHeader w="25%">Destination tx</TableColumnHeader>
|
||||
<TableColumnHeader w="25%">Sender</TableColumnHeader>
|
||||
<TableColumnHeader w="32px"/>
|
||||
<TableColumnHeader w="25%">Target</TableColumnHeader>
|
||||
<TableColumnHeader w="130px">Value</TableColumnHeader>
|
||||
</TableRow>
|
||||
</TableHeaderSticky>
|
||||
<TableBody>
|
||||
{ socketType && <TxsSocketNotice type={ socketType } place="table" isLoading={ isLoading }/> }
|
||||
{ items.map((item, index) => (
|
||||
<CrossChainTxsTableItem
|
||||
key={ String(item.nonce) + (isLoading ? index : '') }
|
||||
item={ item }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CrossChainTxsTable);
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Spinner, VStack } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type * as multichain from '@blockscout/multichain-aggregator-types';
|
||||
import type { TransactionType } from 'types/api/transaction';
|
||||
|
||||
import multichainConfig from 'configs/multichain';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import { Badge } from 'toolkit/chakra/badge';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { TableCell, TableRow } from 'toolkit/chakra/table';
|
||||
import CrossChainTxStatusTag from 'ui/optimismSuperchain/components/CrossChainTxStatusTag';
|
||||
import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton';
|
||||
import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
|
||||
import TxType from 'ui/txs/TxType';
|
||||
|
||||
interface Props {
|
||||
item: multichain.InteropMessage;
|
||||
isLoading: boolean;
|
||||
animation?: string;
|
||||
}
|
||||
|
||||
const CrossChainTxsTableItem = ({ item, isLoading, animation }: Props) => {
|
||||
|
||||
const sourceChain = React.useMemo(() => {
|
||||
const config = multichainConfig();
|
||||
return config?.chains.find((chain) => chain.config.chain.id === item.init_chain_id);
|
||||
}, [ item ]);
|
||||
|
||||
const targetChain = React.useMemo(() => {
|
||||
const config = multichainConfig();
|
||||
return config?.chains.find((chain) => chain.config.chain.id === item.relay_chain_id);
|
||||
}, [ item ]);
|
||||
|
||||
const value = getCurrencyValue({
|
||||
value: item.transfer?.total?.value ?? '0',
|
||||
decimals: '18',
|
||||
});
|
||||
|
||||
return (
|
||||
<TableRow animation={ animation }>
|
||||
<TableCell pl={ 4 }>
|
||||
<AdditionalInfoButton loading={ isLoading }/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<VStack alignItems="start">
|
||||
<Link fontWeight="700" loading={ isLoading }>{ item.nonce }</Link>
|
||||
<TimeWithTooltip
|
||||
timestamp={ item.timestamp }
|
||||
isLoading={ isLoading }
|
||||
color="text.secondary"
|
||||
/>
|
||||
</VStack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<VStack alignItems="start">
|
||||
<TxType types={ [ item.message_type as TransactionType ] } isLoading={ isLoading }/>
|
||||
<CrossChainTxStatusTag status={ item.status } loading={ isLoading }/>
|
||||
</VStack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge colorPalette="gray" loading={ isLoading } truncated>{ item.method }</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{ item.init_transaction_hash ? (
|
||||
<TxEntity
|
||||
hash={ item.init_transaction_hash }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
chain={ sourceChain }
|
||||
/>
|
||||
) :
|
||||
<Spinner size="md"/>
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{ item.relay_transaction_hash ? (
|
||||
<TxEntity
|
||||
hash={ item.relay_transaction_hash }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
chain={ targetChain }
|
||||
/>
|
||||
) :
|
||||
<Spinner size="md"/>
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{ item.sender ? (
|
||||
<AddressEntity
|
||||
address={{ hash: item.sender.hash }}
|
||||
isLoading={ isLoading }
|
||||
chain={ sourceChain }
|
||||
/>
|
||||
) : '-' }
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<AddressFromToIcon isLoading={ isLoading } type="unspecified"/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{ item.target ? (
|
||||
<AddressEntity
|
||||
address={{ hash: item.target.hash }}
|
||||
isLoading={ isLoading }
|
||||
chain={ targetChain }
|
||||
/>
|
||||
) : '-' }
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{ value.valueStr }
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CrossChainTxsTableItem);
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Box, HStack } from '@chakra-ui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
import type { SocketMessage } from 'lib/socket/types';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import useSocketChannel from 'lib/socket/useSocketChannel';
|
||||
import useSocketMessage from 'lib/socket/useSocketMessage';
|
||||
import { BLOCK } from 'stubs/block';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
|
||||
|
||||
interface Props {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
const ChainLatestBlockInfo = ({ slug }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const blocksQuery = useApiQuery('general:homepage_blocks', {
|
||||
chainSlug: slug,
|
||||
queryOptions: {
|
||||
placeholderData: [ BLOCK ],
|
||||
},
|
||||
});
|
||||
|
||||
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
|
||||
const queryKey = getResourceKey('general:homepage_blocks', { chainSlug: slug });
|
||||
queryClient.setQueryData(queryKey, () => {
|
||||
return [ payload.block ];
|
||||
});
|
||||
}, [ queryClient, slug ]);
|
||||
|
||||
const channel = useSocketChannel({
|
||||
topic: 'blocks:new_block',
|
||||
isDisabled: blocksQuery.isPlaceholderData || blocksQuery.isError,
|
||||
});
|
||||
useSocketMessage({
|
||||
channel,
|
||||
event: 'new_block',
|
||||
handler: handleNewBlockMessage,
|
||||
});
|
||||
|
||||
if (!blocksQuery.data?.[0]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack gap={ 2 }>
|
||||
<Box color="text.secondary">Latest block</Box>
|
||||
<Link
|
||||
loading={ blocksQuery.isPlaceholderData }
|
||||
href={ route({
|
||||
pathname: '/chain/[chain-slug]/block/[height_or_hash]',
|
||||
query: {
|
||||
'chain-slug': slug,
|
||||
height_or_hash: blocksQuery.data[0].height.toString(),
|
||||
},
|
||||
}) }
|
||||
>
|
||||
{ blocksQuery.data[0].height }
|
||||
</Link>
|
||||
<TimeWithTooltip
|
||||
timestamp={ blocksQuery.data[0].timestamp }
|
||||
enableIncrement={ !blocksQuery.isPlaceholderData }
|
||||
isLoading={ blocksQuery.isPlaceholderData }
|
||||
color="text.secondary"
|
||||
flexShrink={ 0 }
|
||||
timeFormat="relative"
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChainLatestBlockInfo);
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Box, HStack, VStack } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ChainConfig } from 'types/multichain';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { HOMEPAGE_STATS } from 'stubs/stats';
|
||||
import { Heading } from 'toolkit/chakra/heading';
|
||||
import { Image } from 'toolkit/chakra/image';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import GasPrice from 'ui/shared/gas/GasPrice';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import ChainLatestBlockInfo from './ChainLatestBlockInfo';
|
||||
|
||||
interface Props {
|
||||
data: ChainConfig;
|
||||
}
|
||||
|
||||
const ChainWidget = ({ data }: Props) => {
|
||||
const statsQuery = useApiQuery('general:stats', {
|
||||
chainSlug: data.slug,
|
||||
queryOptions: {
|
||||
placeholderData: HOMEPAGE_STATS,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }}
|
||||
borderRadius="xl"
|
||||
border="1px solid"
|
||||
borderColor={{ _light: 'gray.200', _dark: 'gray.900' }}
|
||||
p={ 4 }
|
||||
flexBasis="50%"
|
||||
textStyle="sm"
|
||||
>
|
||||
<HStack justifyContent="space-between">
|
||||
<Image src={ data.config.UI.navigation.icon.default } alt={ data.config.chain.name } boxSize="30px" borderRadius="full"/>
|
||||
<Link
|
||||
href={ data.config.app.baseUrl }
|
||||
target="_blank"
|
||||
p={ 1 }
|
||||
color="gray.500"
|
||||
_hover={{
|
||||
color: 'link.primary.hover',
|
||||
}}
|
||||
bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }}
|
||||
borderRadius="base"
|
||||
>
|
||||
<IconSvg name="globe" boxSize={ 6 }/>
|
||||
</Link>
|
||||
</HStack>
|
||||
<Heading mt={ 3 } level="3">{ data.config.chain.name }</Heading>
|
||||
<VStack gap={ 2 } mt={ 3 } alignItems="flex-start">
|
||||
<HStack gap={ 2 }>
|
||||
<Box color="text.secondary">Chain ID</Box>
|
||||
<Box>{ data.config.chain.id }</Box>
|
||||
<CopyToClipboard text={ String(data.config.chain.id) } ml={ 0 }/>
|
||||
</HStack>
|
||||
<ChainLatestBlockInfo slug={ data.slug }/>
|
||||
{ statsQuery.data && statsQuery.data.gas_prices && data.config.features.gasTracker.isEnabled && (
|
||||
<HStack gap={ 2 }>
|
||||
<Box color="text.secondary">Gas price</Box>
|
||||
<Skeleton loading={ statsQuery.isPlaceholderData }>
|
||||
<GasPrice data={ statsQuery.data.gas_prices.average }/>
|
||||
</Skeleton>
|
||||
</HStack>
|
||||
) }
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChainWidget);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Box, HStack } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import multichainConfig from 'configs/multichain';
|
||||
import getSocketUrl from 'lib/api/getSocketUrl';
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import HeroBanner from 'ui/home/HeroBanner';
|
||||
|
||||
import ChainWidget from './ChainWidget';
|
||||
import LatestTxs from './LatestTxs';
|
||||
|
||||
const HomeOpSuperchain = () => {
|
||||
return (
|
||||
<Box as="main">
|
||||
<HeroBanner/>
|
||||
<HStack mt={ 3 } gap={ 6 }>
|
||||
{ multichainConfig()?.chains.map(chain => {
|
||||
return (
|
||||
<MultichainProvider key={ chain.slug } chainSlug={ chain.slug }>
|
||||
<SocketProvider url={ getSocketUrl(chain.config) }>
|
||||
<ChainWidget data={ chain }/>
|
||||
</SocketProvider>
|
||||
</MultichainProvider>
|
||||
);
|
||||
}) }
|
||||
</HStack>
|
||||
<LatestTxs/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(HomeOpSuperchain);
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import multichainConfig from 'configs/multichain';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { Heading } from 'toolkit/chakra/heading';
|
||||
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
|
||||
import ChainSelect from 'ui/shared/multichain/ChainSelect';
|
||||
|
||||
import LatestTxsCrossChain from './LatestTxsCrossChain';
|
||||
import LatestTxsLocal from './LatestTxsLocal';
|
||||
|
||||
const LatestTxs = () => {
|
||||
const router = useRouter();
|
||||
const tab = getQueryParamString(router.query.tab);
|
||||
|
||||
const [ chainValue, setChainValue ] = React.useState<Array<string> | undefined>(
|
||||
[ getQueryParamString(router.query['chain-slug']) ?? multichainConfig()?.chains[0]?.slug ].filter(Boolean),
|
||||
);
|
||||
|
||||
const handleChainValueChange = React.useCallback(({ value }: { value: Array<string> }) => {
|
||||
setChainValue(value);
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
'chain-slug': value[0],
|
||||
},
|
||||
}, undefined, { shallow: true });
|
||||
}, [ router ]);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'cross_chain_txs',
|
||||
title: 'Cross-chain',
|
||||
component: <LatestTxsCrossChain/>,
|
||||
},
|
||||
{
|
||||
id: 'local_txs',
|
||||
title: 'Local',
|
||||
component: chainValue ? <LatestTxsLocal key={ chainValue[0] } chainSlug={ chainValue[0] }/> : null,
|
||||
},
|
||||
];
|
||||
|
||||
const rightSlot = tab === 'local_txs' ? (
|
||||
<ChainSelect
|
||||
loading={ false }
|
||||
value={ chainValue }
|
||||
onValueChange={ handleChainValueChange }
|
||||
w="fit-content"
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Box as="section" mt={ 8 }>
|
||||
<Heading level="3" mb={ 6 }>Latest transactions</Heading>
|
||||
<RoutedTabs
|
||||
tabs={ tabs }
|
||||
rightSlot={ rightSlot }
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(LatestTxs);
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import { INTEROP_MESSAGE } from 'stubs/optimismSuperchain';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import CrossChainTxsTable from 'ui/optimismSuperchain/crossChainTxs/CrossChainTxsTable';
|
||||
import DataListDisplay from 'ui/shared/DataListDisplay';
|
||||
|
||||
const socketUrl = config.apis.multichain?.socketEndpoint ? `${ config.apis.multichain.socketEndpoint }/socket` : undefined;
|
||||
|
||||
const LatestTxsCrossChain = () => {
|
||||
|
||||
const { data, isError, isPlaceholderData } = useApiQuery('multichain:interop_messages', {
|
||||
queryOptions: {
|
||||
placeholderData: generateListStub<'multichain:interop_messages'>(INTEROP_MESSAGE, 5, { next_page_params: undefined }),
|
||||
select: (data) => ({ ...data, items: data.items.slice(0, 5) }),
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Box hideFrom="lg">
|
||||
Coming soon 🔜
|
||||
</Box>
|
||||
<Box hideBelow="lg">
|
||||
<CrossChainTxsTable
|
||||
isLoading={ isPlaceholderData }
|
||||
items={ data.items }
|
||||
socketType="txs_home_cross_chain"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<SocketProvider url={ socketUrl }>
|
||||
<DataListDisplay
|
||||
itemsNum={ data?.items?.length }
|
||||
isError={ isError }
|
||||
emptyText="There are no cross-chain transactions."
|
||||
>
|
||||
{ content }
|
||||
</DataListDisplay>
|
||||
</SocketProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(LatestTxsCrossChain);
|
||||
@@ -0,0 +1,58 @@
|
||||
import { noop } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import type { PaginationParams } from 'ui/shared/pagination/types';
|
||||
|
||||
import multichainConfig from 'configs/multichain';
|
||||
import getSocketUrl from 'lib/api/getSocketUrl';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { MultichainProvider } from 'lib/contexts/multichain';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import { TX } from 'stubs/tx';
|
||||
import TxsContent from 'ui/txs/TxsContent';
|
||||
|
||||
const PAGINATION_PARAMS: PaginationParams = {
|
||||
page: 1,
|
||||
isVisible: false,
|
||||
isLoading: false,
|
||||
hasPages: false,
|
||||
hasNextPage: false,
|
||||
canGoBackwards: false,
|
||||
onNextPageClick: () => {},
|
||||
onPrevPageClick: () => {},
|
||||
resetPage: () => {},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
chainSlug: string;
|
||||
}
|
||||
|
||||
const LatestTxsLocal = ({ chainSlug }: Props) => {
|
||||
const query = useApiQuery('general:homepage_txs', {
|
||||
chainSlug,
|
||||
queryOptions: {
|
||||
placeholderData: Array(5).fill(TX),
|
||||
select: (data) => data.slice(0, 5),
|
||||
},
|
||||
});
|
||||
|
||||
const chainData = multichainConfig()?.chains.find(chain => chain.slug === chainSlug);
|
||||
|
||||
return (
|
||||
<MultichainProvider chainSlug={ chainSlug }>
|
||||
<SocketProvider url={ getSocketUrl(chainData?.config) }>
|
||||
<TxsContent
|
||||
items={ query.data || [] }
|
||||
isPlaceholderData={ query.isPlaceholderData }
|
||||
isError={ query.isError }
|
||||
pagination={ PAGINATION_PARAMS }
|
||||
setSorting={ noop }
|
||||
sort="default"
|
||||
socketType="txs_home"
|
||||
/>
|
||||
</SocketProvider>
|
||||
</MultichainProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(LatestTxsLocal);
|
||||
@@ -11,6 +11,7 @@ import useAddressMetadataInfoQuery from 'lib/address/useAddressMetadataInfoQuery
|
||||
import useAddressMetadataInitUpdate from 'lib/address/useAddressMetadataInitUpdate';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { useAppContext } from 'lib/contexts/app';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import useAddressProfileApiQuery from 'lib/hooks/useAddressProfileApiQuery';
|
||||
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
|
||||
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
|
||||
@@ -69,6 +70,7 @@ const xScoreFeature = config.features.xStarScore;
|
||||
const AddressPageContent = () => {
|
||||
const router = useRouter();
|
||||
const appProps = useAppContext();
|
||||
const { chain } = useMultichainContext() || {};
|
||||
|
||||
const hash = getQueryParamString(router.query.hash);
|
||||
|
||||
@@ -437,11 +439,13 @@ const AddressPageContent = () => {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const chainText = chain ? ` on ${ chain.config.chain.name }` : '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
<PageTitle
|
||||
title={ `${ addressQuery.data?.is_contract && addressQuery.data?.proxy_type !== 'eip7702' ? 'Contract' : 'Address' } details` }
|
||||
title={ `${ addressQuery.data?.is_contract && addressQuery.data?.proxy_type !== 'eip7702' ? 'Contract' : 'Address' } details${ chainText }` }
|
||||
backLink={ backLink }
|
||||
contentAfter={ titleContentAfter }
|
||||
secondRow={ titleSecondRow }
|
||||
|
||||
+7
-3
@@ -8,6 +8,7 @@ import type { PaginationParams } from 'ui/shared/pagination/types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { useAppContext } from 'lib/contexts/app';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError';
|
||||
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
@@ -46,6 +47,7 @@ const BlockPageContent = () => {
|
||||
const appProps = useAppContext();
|
||||
const heightOrHash = getQueryParamString(router.query.height_or_hash);
|
||||
const tab = getQueryParamString(router.query.tab);
|
||||
const { chain } = useMultichainContext() || {};
|
||||
|
||||
const blockQuery = useBlockQuery({ heightOrHash });
|
||||
const blockTxsQuery = useBlockTxsQuery({ heightOrHash, blockQuery, tab });
|
||||
@@ -145,15 +147,17 @@ const BlockPageContent = () => {
|
||||
}
|
||||
|
||||
const title = (() => {
|
||||
const chainText = chain ? ` on ${ chain.config.chain.name }` : '';
|
||||
|
||||
switch (blockQuery.data?.type) {
|
||||
case 'reorg':
|
||||
return `Reorged block #${ blockQuery.data?.height }`;
|
||||
return `Reorged block #${ blockQuery.data?.height }${ chainText }`;
|
||||
|
||||
case 'uncle':
|
||||
return `Uncle block #${ blockQuery.data?.height }`;
|
||||
return `Uncle block #${ blockQuery.data?.height }${ chainText }`;
|
||||
|
||||
default:
|
||||
return `Block #${ blockQuery.data?.height }`;
|
||||
return `Block #${ blockQuery.data?.height }${ chainText }`;
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { EntityTag as TEntityTag } from 'ui/shared/EntityTags/types';
|
||||
import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { useAppContext } from 'lib/contexts/app';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import useEtherscanRedirects from 'lib/router/useEtherscanRedirects';
|
||||
@@ -38,6 +39,7 @@ const tacFeature = config.features.tac;
|
||||
const TransactionPageContent = () => {
|
||||
const router = useRouter();
|
||||
const appProps = useAppContext();
|
||||
const { chain } = useMultichainContext() || {};
|
||||
|
||||
const hash = getQueryParamString(router.query.hash);
|
||||
|
||||
@@ -133,7 +135,7 @@ const TransactionPageContent = () => {
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
<PageTitle
|
||||
title="Transaction details"
|
||||
title={ chain ? `Transaction details on ${ chain.config.chain.name }` : 'Transaction details' }
|
||||
backLink={ backLink }
|
||||
contentAfter={ tags }
|
||||
secondRow={ titleSecondRow }
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
|
||||
import type { EntityTag as TEntityTag } from './types';
|
||||
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import { Link, LinkExternalIcon } from 'toolkit/chakra/link';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
@@ -19,8 +20,9 @@ interface Props extends HTMLChakraProps<'span'> {
|
||||
}
|
||||
|
||||
const EntityTag = ({ data, isLoading, noLink, ...rest }: Props) => {
|
||||
const multichainContext = useMultichainContext();
|
||||
|
||||
const linkParams = !noLink ? getTagLinkParams(data) : undefined;
|
||||
const linkParams = !noLink ? getTagLinkParams(data, multichainContext) : undefined;
|
||||
const hasLink = Boolean(linkParams);
|
||||
const iconColor = data.meta?.textColor ?? 'gray.400';
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { EntityTag } from './types';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
import { route } from 'nextjs/routes';
|
||||
|
||||
export function getTagLinkParams(data: EntityTag): { type: 'external' | 'internal'; href: string } | undefined {
|
||||
import type { TMultichainContext } from 'lib/contexts/multichain';
|
||||
|
||||
export function getTagLinkParams(data: EntityTag, multichainContext?: TMultichainContext | null): { type: 'external' | 'internal'; href: string } | undefined {
|
||||
if (data.meta?.warpcastHandle) {
|
||||
return {
|
||||
type: 'external',
|
||||
@@ -20,7 +22,7 @@ export function getTagLinkParams(data: EntityTag): { type: 'external' | 'interna
|
||||
if (data.tagType === 'generic' || data.tagType === 'protocol') {
|
||||
return {
|
||||
type: 'internal',
|
||||
href: route({ pathname: '/accounts/label/[slug]', query: { slug: data.slug, tagType: data.tagType, tagName: data.name } }),
|
||||
href: route({ pathname: '/accounts/label/[slug]', query: { slug: data.slug, tagType: data.tagType, tagName: data.name } }, multichainContext),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
import { WagmiProvider } from 'wagmi';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { currentChain, parentChain } from 'lib/web3/chains';
|
||||
import { currentChain, parentChain, clusterChains } from 'lib/web3/chains';
|
||||
import wagmiConfig from 'lib/web3/wagmiConfig';
|
||||
import { useColorMode } from 'toolkit/chakra/color-mode';
|
||||
import colors from 'toolkit/theme/foundations/colors';
|
||||
@@ -19,9 +19,11 @@ const init = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const networks = [ currentChain, parentChain, ...(clusterChains ?? []) ].filter(Boolean) as [AppKitNetwork, ...Array<AppKitNetwork>];
|
||||
|
||||
createAppKit({
|
||||
adapters: [ wagmiConfig.adapter ],
|
||||
networks: [ currentChain, parentChain ].filter(Boolean) as [AppKitNetwork, ...Array<AppKitNetwork>],
|
||||
networks,
|
||||
metadata: {
|
||||
name: `${ config.chain.name } explorer`,
|
||||
description: `${ config.chain.name } explorer`,
|
||||
|
||||
@@ -3,10 +3,11 @@ import React from 'react';
|
||||
|
||||
import type { AddressParam } from 'types/api/addressParams';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
import { route } from 'nextjs/routes';
|
||||
|
||||
import { toBech32Address } from 'lib/address/bech32';
|
||||
import { useAddressHighlightContext } from 'lib/contexts/addressHighlight';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import { useSettingsContext } from 'lib/contexts/settings';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import { Tooltip } from 'toolkit/chakra/tooltip';
|
||||
@@ -24,7 +25,10 @@ const getDisplayedAddress = (address: AddressProp, altHash?: string) => {
|
||||
};
|
||||
|
||||
const Link = chakra((props: LinkProps) => {
|
||||
const defaultHref = route({ pathname: '/address/[hash]', query: { ...props.query, hash: props.address.hash } });
|
||||
const defaultHref = route(
|
||||
{ pathname: '/address/[hash]', query: { ...props.query, hash: props.address.hash } },
|
||||
props.chain ? { chain: props.chain } : undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<EntityBase.Link
|
||||
@@ -43,7 +47,10 @@ const Icon = (props: IconProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const marginRight = props.marginRight ?? (props.shield ? '18px' : '8px');
|
||||
const shield = props.shield ?? (props.chain ? { src: props.chain.config.UI.navigation.icon.default } : undefined);
|
||||
const hintPostfix: string = props.hintPostfix ?? (props.chain ? ` on ${ props.chain.config.chain.name } (Chain ID: ${ props.chain.config.chain.id })` : '');
|
||||
|
||||
const marginRight = props.marginRight ?? (shield ? '18px' : '8px');
|
||||
const styles = {
|
||||
...getIconProps(props.variant),
|
||||
marginRight,
|
||||
@@ -60,6 +67,7 @@ const Icon = (props: IconProps) => {
|
||||
return (
|
||||
<EntityBase.Icon
|
||||
{ ...props }
|
||||
shield={ shield }
|
||||
name="brands/safe"
|
||||
/>
|
||||
);
|
||||
@@ -68,11 +76,12 @@ const Icon = (props: IconProps) => {
|
||||
const isProxy = Boolean(props.address.implementations?.length);
|
||||
const isVerified = isProxy ? props.address.is_verified && props.address.implementations?.every(({ name }) => Boolean(name)) : props.address.is_verified;
|
||||
const contractIconName: EntityBase.IconBaseProps['name'] = props.address.is_verified ? 'contracts/verified' : 'contracts/regular';
|
||||
const label = (isVerified ? 'verified ' : '') + (isProxy ? 'proxy contract' : 'contract') + (props.hintPostfix ?? '');
|
||||
const label = (isVerified ? 'verified ' : '') + (isProxy ? 'proxy contract' : 'contract') + hintPostfix;
|
||||
|
||||
return (
|
||||
<EntityBase.Icon
|
||||
{ ...props }
|
||||
shield={ shield }
|
||||
name={ isProxy ? 'contracts/proxy' : contractIconName }
|
||||
color={ isVerified ? 'green.500' : undefined }
|
||||
borderRadius={ 0 }
|
||||
@@ -83,7 +92,11 @@ const Icon = (props: IconProps) => {
|
||||
|
||||
const label = (() => {
|
||||
if (isDelegatedAddress) {
|
||||
return (props.address.is_verified ? 'EOA + verified code' : 'EOA + code') + (props.hintPostfix ?? '');
|
||||
return (props.address.is_verified ? 'EOA + verified code' : 'EOA + code') + hintPostfix;
|
||||
}
|
||||
|
||||
if (props.chain) {
|
||||
return 'Address' + hintPostfix;
|
||||
}
|
||||
|
||||
return props.hint;
|
||||
@@ -94,14 +107,14 @@ const Icon = (props: IconProps) => {
|
||||
content={ label }
|
||||
disabled={ !label }
|
||||
interactive={ props.tooltipInteractive }
|
||||
positioning={ props.shield ? { offset: { mainAxis: 8 } } : undefined }
|
||||
positioning={ shield ? { offset: { mainAxis: 8 } } : undefined }
|
||||
>
|
||||
<Flex marginRight={ styles.marginRight } position="relative">
|
||||
<AddressIdenticon
|
||||
size={ props.variant === 'heading' ? 30 : 20 }
|
||||
hash={ getDisplayedAddress(props.address) }
|
||||
/>
|
||||
{ props.shield && <EntityBase.IconShield { ...props.shield }/> }
|
||||
{ shield && <EntityBase.IconShield { ...shield }/> }
|
||||
{ isDelegatedAddress && <AddressIconDelegated isVerified={ Boolean(props.address.is_verified) }/> }
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
@@ -183,7 +196,10 @@ const AddressEntity = (props: EntityProps) => {
|
||||
const partsProps = distributeEntityProps(props);
|
||||
const highlightContext = useAddressHighlightContext(props.noHighlight);
|
||||
const settingsContext = useSettingsContext();
|
||||
const multichainContext = useMultichainContext();
|
||||
|
||||
const altHash = !props.noAltHash && settingsContext?.addressFormat === 'bech32' ? toBech32Address(props.address.hash) : undefined;
|
||||
const chain = props.chain ?? multichainContext?.chain;
|
||||
|
||||
// inside highlight context all tooltips should be interactive
|
||||
// because non-interactive ones will not pass 'onMouseLeave' event to the parent component
|
||||
@@ -201,8 +217,8 @@ const AddressEntity = (props: EntityProps) => {
|
||||
position="relative"
|
||||
zIndex={ 0 }
|
||||
>
|
||||
<Icon { ...partsProps.icon } tooltipInteractive={ Boolean(highlightContext) }/>
|
||||
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
|
||||
<Icon { ...partsProps.icon } tooltipInteractive={ Boolean(highlightContext) } chain={ chain }/>
|
||||
{ props.noLink ? content : <Link { ...partsProps.link } chain={ chain }>{ content }</Link> }
|
||||
<Copy { ...partsProps.copy } altHash={ altHash } tooltipInteractive={ Boolean(highlightContext) }/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user