Fixes for release v2.5.0 (#3142)

* fail the app start if multichain config or essential dapps config were not generated properly

* add timeouts for essential dapps and multichain config generators

* fix "add to wallet" button in address tokens table

* [skip ci] update docs

* fix tx tables when there are no fee and value columns

* add support for promoted multichain provider

* fixes

* change action button logic

* fix tests

* fix

* hide "add network" button for multichain

* shorten the amount of txs on multichain homepage

* change name formatting for multichain button

* add bright color variants and lg size for badges

* fix y-axis margin calculation for chart with custom tick formatter

* superchain bug fixes

* bump versions of react and nextjs

* update screenshot

* bump dappscout-iframe

---------

Co-authored-by: Max Alekseenko <[email protected]>
This commit is contained in:
tom goriunov
2025-12-04 18:44:01 +01:00
committed by GitHub
co-authored by Max Alekseenko
parent 2469406682
commit 9a977d191b
58 changed files with 662 additions and 273 deletions
+9 -6
View File
@@ -13,12 +13,15 @@ const config: Feature<{ providers: Array<MultichainProviderConfigParsed> }> = ((
return Object.freeze({
title,
isEnabled: true,
providers: value.map((provider) => ({
name: provider.name,
logoUrl: provider.logo,
urlTemplate: provider.url_template,
dappId: marketplace.isEnabled ? provider.dapp_id : undefined,
})),
providers: value
.map((provider) => ({
name: provider.name,
logoUrl: provider.logo,
urlTemplate: provider.url_template,
dappId: marketplace.isEnabled ? provider.dapp_id : undefined,
promo: provider.promo,
}))
.sort((_, b) => (b.promo ? 1 : -1)),
});
}
+1 -1
View File
@@ -48,7 +48,7 @@ NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKj
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_METASUITES_ENABLED=true
NEXT_PUBLIC_MIXPANEL_CONFIG_OVERRIDES={"record_sessions_percent": 0.5,"record_heatmap_data": true}
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'},{'name': 'zapper', 'url_template': 'https://zapper.xyz/account/{address}', 'logo': 'https://blockscout-content.s3.amazonaws.com/zapper-icon.png'}]
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'},{'name': 'zapper', 'url_template': 'https://zapper.xyz/account/{address}', 'logo': 'https://blockscout-content.s3.amazonaws.com/zapper-icon.png'},{'name': 'blockscout', 'url_template': 'https://superchain.blockscout.com/address/{address}', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/multichain-balance/blockscout.svg', 'promo': true}]
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
NEXT_PUBLIC_NAVIGATION_PROMO_BANNER_CONFIG={"img_url": {"small": "https://blockscout-merits-images.s3.us-east-1.amazonaws.com/banners/sidemenu-banner-small.png", "large": "https://blockscout-merits-images.s3.us-east-1.amazonaws.com/banners/sidemenu-banner-big.png"}, "link_url": "https://www.blockscout.com/?utm_source=blockscout&utm_medium=side-menu-banner"}
+12 -4
View File
@@ -69,19 +69,27 @@ node --no-warnings ./og_image_generator.js
./make_envs_script.sh
# Generate multichain config
node ./deploy/tools/multichain-config-generator/dist/index.js
node --no-warnings ./deploy/tools/multichain-config-generator/dist/index.js
if [ $? -ne 0 ]; then
echo "👎 Unable to generate multichain config."
exit 1
fi
# Generate essential dapps chains config
node ./deploy/tools/essential-dapps-chains-config-generator/dist/index.js
node --no-warnings ./deploy/tools/essential-dapps-chains-config-generator/dist/index.js
if [ $? -ne 0 ]; then
echo "👎 Unable to generate essential dapps chains config."
exit 1
fi
# Generate sitemap.xml and robots.txt files
./sitemap_generator.sh
# Generate llms.txt file
node ./deploy/tools/llms-txt-generator/dist/index.js
node --no-warnings ./deploy/tools/llms-txt-generator/dist/index.js
# Print list of enabled features
node ./feature-reporter.js
node --no-warnings ./feature-reporter.js
echo "Starting Next.js application"
exec "$@"
+1
View File
@@ -35,6 +35,7 @@ const multichainProviderConfigSchema: yup.ObjectSchema<MultichainProviderConfig>
url_template: yup.string().required(),
logo: yup.string().required(),
dapp_id: yup.string(),
promo: yup.boolean(),
});
const schema = yup
@@ -17,23 +17,39 @@ const currentFilePath = fileURLToPath(import.meta.url);
const currentDir = dirname(currentFilePath);
async function getChainscoutInfo(externalChainIds: Array<string>, currentChainId: string | undefined) {
const response = await fetch('https://chains.blockscout.com/api/chains');
if (!response.ok) {
throw new Error(`Failed to fetch chains info from Chainscout API`);
}
const chainsInfo = await response.json() as Record<string, { explorers: [ { url: string } ], logo: string }>;
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(`Request to Chainscout API timed out`);
}, 10_000);
return {
externals: externalChainIds.map((chainId) => ({
id: chainId,
explorerUrl: chainsInfo[chainId]?.explorers[0]?.url,
logoUrl: chainsInfo[chainId]?.logo,
})),
current: currentChainId ? {
id: currentChainId,
explorerUrl: chainsInfo[currentChainId]?.explorers[0]?.url,
logoUrl: chainsInfo[currentChainId]?.logo,
} : undefined,
try {
const response = await fetch(
`https://chains.blockscout.com/api/chains?chain_ids=${ [currentChainId, ...externalChainIds].filter(Boolean).join(',') }`,
{ signal: controller.signal }
);
if (!response.ok) {
throw new Error(`Failed to fetch chains info from Chainscout API`);
}
const chainsInfo = await response.json() as Record<string, { explorers: [ { url: string } ], logo: string }>;
return {
externals: externalChainIds.map((chainId) => ({
id: chainId,
explorerUrl: chainsInfo[chainId]?.explorers[0]?.url,
logoUrl: chainsInfo[chainId]?.logo,
})),
current: currentChainId ? {
id: currentChainId,
explorerUrl: chainsInfo[currentChainId]?.explorers[0]?.url,
logoUrl: chainsInfo[currentChainId]?.logo,
} : undefined,
}
} catch (error) {
throw error;
} finally {
clearTimeout(timeout);
}
}
@@ -10,12 +10,23 @@ interface ChainConfig {
}
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 controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(`Request to ${ url } timed out`);
}, 5_000);
try {
const response = await fetch(`${ url }/node-api/config`, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Failed to fetch config from ${ url }: ${ response.statusText }`);
}
const config = await response.json();
return config as ChainConfig;
} catch (error) {
throw error;
} finally {
clearTimeout(timeout);
}
const config = await response.json();
return config as ChainConfig;
}
async function computeConfig() {
@@ -24,16 +24,27 @@ function getSlug(chainName: string) {
}
async function getChainscoutInfo(chainIds: Array<string>) {
const response = await fetch('https://chains.blockscout.com/api/chains');
if (!response.ok) {
throw new Error(`Failed to fetch chains info from Chainscout API`);
}
const chainsInfo = await response.json() as Record<string, { explorers: [ { url: string } ], logo: string }>;
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(`Request to Chainscout API timed out`);
}, 10_000);
return chainIds.map((chainId) => ({
id: chainId,
logoUrl: chainsInfo[chainId]?.logo,
}))
try {
const response = await fetch(`https://chains.blockscout.com/api/chains?chain_ids=${ chainIds.join(',') }`, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Failed to fetch chains info from Chainscout API`);
}
const chainsInfo = await response.json() as Record<string, { explorers: [ { url: string } ], logo: string }>;
return chainIds.map((chainId) => ({
id: chainId,
logoUrl: chainsInfo[chainId]?.logo,
}))
} catch (error) {
throw error;
} finally {
clearTimeout(timeout);
}
}
async function computeChainConfig(url: string): Promise<unknown> {
@@ -10,12 +10,23 @@ interface ChainConfig {
}
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 controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(`Request to ${ url } timed out`);
}, 5_000);
try {
const response = await fetch(`${ url }/node-api/config`, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Failed to fetch config from ${ url }: ${ response.statusText }`);
}
const config = await response.json();
return config as ChainConfig;
} catch (error) {
throw error;
} finally {
clearTimeout(timeout);
}
const config = await response.json();
return config as ChainConfig;
}
async function computeConfig() {
+3
View File
@@ -102,6 +102,8 @@ All json-like values should be single-quoted. If it contains a hash (`#`) or a d
*Note!* The `NEXT_PUBLIC_NETWORK_CURRENCY` variables represent the blockchain's native token used for paying transaction fees. `NEXT_PUBLIC_NETWORK_SECONDARY_COIN` variables refer to tokens like protocol-specific tokens (e.g., OP token on Optimism chain) or governance tokens (e.g., GNO on Gnosis chain).
Also, be aware that if you customize the name of the currency or any of its denominations (wei or gwei) while running Stats microservices, you may want to change those names in the indicators and charts returned by the microservice. To do this, pass the appropriate values to the Stats microservice environment variables, such as `STATS_CHARTS__LINE_CHARTS__<LINE_CHART_NAME>__UNITS` and `STATS_CHARTS__LINE_CHARTS__<LINE_CHART_NAME>__DESCRIPTION`. For the Average Gas Price chart, the `<LINE_CHART_NAME>` will be `average_gas_price`. Please refer to the [microservice documentation](https://github.com/blockscout/blockscout-rs/tree/main/stats#charts) for the complete list of these variables.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` | v1.0.x+ |
@@ -913,6 +915,7 @@ If the feature is enabled, a Multichain balance button will be displayed on the
| url_template | `string` | Url template to the portfolio. Should be a template with `{address}` variable | Required | - | `https://app.zerion.io/{address}/overview` |
| dapp_id | `string` | Set for open a Blockscout dapp page with the portfolio instead of opening external app page | - | - | `zerion` |
| logo | `string` | Multichain portfolio application logo (.svg) url | - | - | `https://example.com/icon.svg` |
| promo | `boolean` | Make the provider stand out by placing their logo prominently at the first place in the section and in the page subheader. | - | - | `true` |
&nbsp;
+1 -2
View File
@@ -9,12 +9,12 @@ const bgColor = '#FF007A';
export const buttonWithoutStyles: AddressMetadataTagApi['meta'] = {
appID,
appMarketplaceURL,
appLogoURL,
appActionButtonText,
};
export const linkWithoutStyles: AddressMetadataTagApi['meta'] = {
appID,
appMarketplaceURL,
appLogoURL,
appActionButtonText,
@@ -22,7 +22,6 @@ export const linkWithoutStyles: AddressMetadataTagApi['meta'] = {
export const buttonWithStyles: AddressMetadataTagApi['meta'] = {
appID,
appMarketplaceURL,
appLogoURL,
appActionButtonText,
textColor,
+4 -4
View File
@@ -93,7 +93,7 @@
"chakra-react-select": "^4.4.3",
"crypto-js": "^4.2.0",
"d3": "^7.6.1",
"dappscout-iframe": "0.3.0",
"dappscout-iframe": "0.3.2",
"dayjs": "^1.11.5",
"dom-to-image": "^2.6.0",
"es-toolkit": "1.39.10",
@@ -107,7 +107,7 @@
"magic-bytes.js": "1.8.0",
"mixpanel-browser": "2.67.0",
"monaco-editor": "^0.34.1",
"next": "15.5.2",
"next": "15.5.7",
"next-themes": "0.4.4",
"nextjs-routes": "^1.0.8",
"node-fetch": "^3.2.9",
@@ -118,9 +118,9 @@
"pino-pretty": "^9.1.1",
"prom-client": "15.1.1",
"qrcode": "^1.5.1",
"react": "19.1.0",
"react": "19.1.2",
"react-device-detect": "^2.2.3",
"react-dom": "19.1.0",
"react-dom": "19.1.2",
"react-google-recaptcha": "3.1.0",
"react-hook-form": "7.52.1",
"react-icons": "5.4.0",
-4
View File
@@ -1,4 +0,0 @@
<svg viewBox="0 0 42 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="42" height="14" rx="4" fill="#F6E05E"/>
<path d="M37.398 3.718v6.545h-.991V4.708h-.038l-1.566 1.023v-.946l1.633-1.067h.962Zm-9.096 6.545V3.718h4.104v.85H29.29v1.994h2.902v.847H29.29v2.004h3.154v.85h-4.142Zm-2.406-4.478a1.995 1.995 0 0 0-.249-.52 1.593 1.593 0 0 0-.857-.65 1.966 1.966 0 0 0-.597-.086 1.81 1.81 0 0 0-.997.285c-.297.19-.53.468-.704.834-.17.364-.255.81-.255 1.34s.086.978.259 1.345c.172.366.409.644.71.834.3.19.642.284 1.025.284.356 0 .666-.072.93-.217a1.52 1.52 0 0 0 .617-.614c.147-.266.22-.58.22-.94l.256.049h-1.873v-.815h2.573v.744c0 .55-.117 1.027-.351 1.432a2.394 2.394 0 0 1-.966.933c-.409.22-.878.33-1.406.33-.592 0-1.112-.137-1.56-.41a2.772 2.772 0 0 1-1.041-1.16c-.25-.502-.374-1.1-.374-1.79 0-.521.072-.99.217-1.406.145-.415.348-.768.61-1.058a2.7 2.7 0 0 1 .93-.668 2.912 2.912 0 0 1 1.173-.233c.354 0 .683.052.988.157.307.104.58.252.818.444a2.515 2.515 0 0 1 .917 1.556h-1.013Zm-9.781 4.478h-1.048l2.355-6.545h1.141l2.356 6.545H19.87l-1.85-5.356h-.051l-1.854 5.356Zm.176-2.563h3.4v.83h-3.4V7.7Zm-6.088-3.132v-.85h5.065v.85h-2.042v5.695h-.984V4.568h-2.04Zm-1.772.869a.934.934 0 0 0-.421-.703c-.248-.168-.559-.252-.934-.252-.268 0-.5.042-.696.127a1.073 1.073 0 0 0-.457.346.815.815 0 0 0-.16.495c0 .155.036.29.108.403a.975.975 0 0 0 .291.284c.122.075.252.137.39.189.139.049.272.09.4.121l.639.166c.209.051.423.12.642.208.22.087.423.202.61.345.188.143.34.32.454.53.118.212.176.464.176.758 0 .37-.096.7-.287.988-.19.287-.466.514-.828.68-.36.167-.796.25-1.307.25-.49 0-.914-.078-1.272-.233a1.959 1.959 0 0 1-.84-.662 1.918 1.918 0 0 1-.336-1.023h.99c.02.237.096.434.23.591.137.156.31.272.521.349.213.074.447.112.7.112.28 0 .528-.044.745-.131.22-.09.392-.213.518-.371a.878.878 0 0 0 .188-.56.686.686 0 0 0-.166-.476 1.228 1.228 0 0 0-.444-.306 4.342 4.342 0 0 0-.63-.215l-.773-.21c-.524-.143-.94-.353-1.247-.63-.304-.277-.457-.644-.457-1.1 0-.377.103-.706.307-.987.205-.281.482-.5.831-.655.35-.158.744-.237 1.183-.237.443 0 .834.078 1.173.233.34.156.609.37.805.643.196.27.298.582.307.933H8.43Z" fill="#101112" fill-opacity=".8"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg viewBox="0 0 42 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="42" height="14" rx="4" fill="#9AE6B4"/>
<path d="M33.949 10.263v-.716l2.215-2.295c.236-.249.431-.467.585-.655.155-.19.271-.37.348-.54a1.3 1.3 0 0 0 .115-.543 1.01 1.01 0 0 0-.153-.563 1.002 1.002 0 0 0-.419-.367 1.332 1.332 0 0 0-.598-.131c-.234 0-.439.048-.613.144a.985.985 0 0 0-.403.405 1.28 1.28 0 0 0-.14.614h-.943c0-.398.091-.747.275-1.045.183-.298.434-.53.754-.694.32-.166.683-.249 1.09-.249.41 0 .773.082 1.086.246.316.162.562.384.738.665.177.28.266.595.266.946 0 .243-.046.48-.138.713-.09.232-.246.49-.47.776-.223.284-.534.628-.933 1.033l-1.3 1.361v.048h2.946v.847H33.95Zm-6.506 0V3.718h4.103v.85H28.43v1.994h2.902v.847H28.43v2.004h3.155v.85h-4.142Zm-2.407-4.478a1.998 1.998 0 0 0-.249-.52 1.593 1.593 0 0 0-.856-.65 1.967 1.967 0 0 0-.598-.086 1.81 1.81 0 0 0-.997.285c-.296.19-.53.468-.703.834-.17.364-.256.81-.256 1.34s.086.978.259 1.345c.172.366.409.644.71.834.3.19.642.284 1.025.284.356 0 .666-.072.93-.217.267-.145.472-.35.617-.614.147-.266.22-.58.22-.94l.256.049h-1.872v-.815h2.572v.744c0 .55-.117 1.027-.351 1.432a2.393 2.393 0 0 1-.965.933c-.41.22-.878.33-1.407.33-.592 0-1.112-.137-1.56-.41a2.773 2.773 0 0 1-1.041-1.16c-.25-.502-.374-1.1-.374-1.79 0-.521.072-.99.217-1.406.145-.415.348-.768.61-1.058a2.7 2.7 0 0 1 .93-.668 2.911 2.911 0 0 1 1.174-.233c.353 0 .682.052.987.157.307.104.58.252.818.444a2.516 2.516 0 0 1 .917 1.556h-1.013Zm-9.781 4.478h-1.048l2.355-6.545h1.141l2.356 6.545H19.01l-1.85-5.356h-.052l-1.854 5.356Zm.176-2.563h3.4v.83h-3.4V7.7ZM9.38 4.568v-.85h5.067v.85h-2.043v5.695h-.984V4.568H9.38Zm-1.808.869a.934.934 0 0 0-.422-.703c-.248-.168-.559-.252-.934-.252-.268 0-.5.042-.696.127a1.073 1.073 0 0 0-.457.346.815.815 0 0 0-.16.495c0 .155.036.29.109.403a.975.975 0 0 0 .29.284c.122.075.252.137.39.189a4.4 4.4 0 0 0 .4.121l.639.166c.209.051.423.12.642.208.22.087.423.202.61.345a1.537 1.537 0 0 1 .63 1.288c0 .37-.095.7-.287.988-.19.287-.466.514-.828.68-.36.167-.796.25-1.307.25-.49 0-.914-.078-1.272-.233a1.959 1.959 0 0 1-.84-.662 1.918 1.918 0 0 1-.336-1.023h.99c.02.237.096.434.23.591.137.156.31.272.522.349.213.074.446.112.7.112.279 0 .527-.044.744-.131.22-.09.392-.213.518-.371a.878.878 0 0 0 .188-.56.686.686 0 0 0-.166-.476 1.228 1.228 0 0 0-.444-.306 4.342 4.342 0 0 0-.63-.215l-.773-.21c-.524-.143-.94-.353-1.247-.63-.304-.277-.457-.644-.457-1.1 0-.377.103-.706.307-.987.205-.281.482-.5.831-.655.35-.158.744-.237 1.183-.237.443 0 .834.078 1.173.233.34.156.61.37.805.643.196.27.298.582.307.933h-.952Z" fill="#101112" fill-opacity=".8"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

@@ -17,7 +17,7 @@ export interface LabelFormatParams extends Intl.NumberFormatOptions {
type Data = TimeChartData;
export function getAxesParams(data: Data, axesConfig?: AxesConfig) {
const { labelFormatParams: labelFormatParamsY, scale: yScale } = getAxisParamsY(data, axesConfig?.y);
const { labelFormatParams: labelFormatParamsY, scale: yScale } = getAxisParamsY(data, axesConfig?.y, axesConfig?.y?.tickFormatter);
return {
x: {
@@ -60,7 +60,7 @@ const tickFormatterX = (axis: d3.Axis<d3.NumberValue>) => (d: d3.AxisDomain) =>
return format(d as Date);
};
function getAxisParamsY(data: Data, config?: AxisConfig) {
function getAxisParamsY(data: Data, config?: AxisConfig, tickFormatter?: () => (d: d3.AxisDomain) => string) {
const DEFAULT_TICKS_NUM = 3;
const min = d3.min(data, ({ items }) => d3.min(items, ({ value }) => value)) ?? 0;
const max = d3.max(data, ({ items }) => d3.max(items, ({ value }) => value)) ?? 0;
@@ -72,7 +72,7 @@ function getAxisParamsY(data: Data, config?: AxisConfig) {
.domain([ config?.scale?.min ?? min, max ]);
const ticks = scale.ticks(config?.ticks ?? DEFAULT_TICKS_NUM);
const labelFormatParams = getYLabelFormatParams(ticks);
const labelFormatParams = getYLabelFormatParams(ticks, tickFormatter);
return { min, max, scale, labelFormatParams };
}
@@ -82,19 +82,23 @@ const getTickFormatterY = (params: Intl.NumberFormatOptions) => () => (d: d3.Axi
return num.toLocaleString(undefined, params);
};
function getYLabelFormatParams(ticks: Array<number>, maximumSignificantDigits = DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS): LabelFormatParams {
function getYLabelFormatParams(
ticks: Array<number>,
tickFormatter?: () => (d: d3.AxisDomain) => string,
maximumSignificantDigits = DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS,
): LabelFormatParams {
const params = {
maximumFractionDigits: DEFAULT_MAXIMUM_FRACTION_DIGITS,
maximumSignificantDigits,
notation: 'compact' as const,
};
const uniqTicksStr = uniq(ticks.map((tick) => tick.toLocaleString(undefined, params)));
const uniqTicksStr = uniq(ticks.map((tick) => tickFormatter ? tickFormatter()(tick) : tick.toLocaleString(undefined, params)));
const maxLabelLength = maxBy(uniqTicksStr, (items) => items.length)?.length ?? DEFAULT_LABEL_LENGTH;
if (uniqTicksStr.length === ticks.length || maximumSignificantDigits === MAXIMUM_SIGNIFICANT_DIGITS_LIMIT) {
return { ...params, maxLabelLength };
}
return getYLabelFormatParams(ticks, maximumSignificantDigits + 1);
return getYLabelFormatParams(ticks, tickFormatter, maximumSignificantDigits + 1);
}
@@ -396,6 +396,53 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
bg: { value: { _light: '{colors.cyan.50}', _dark: '{colors.cyan.800}' } },
fg: { value: { _light: '{colors.cyan.500}', _dark: '{colors.cyan.100}' } },
},
pink: {
bg: { value: { _light: '{colors.pink.50}', _dark: '{colors.pink.800}' } },
fg: { value: { _light: '{colors.pink.500}', _dark: '{colors.pink.100}' } },
},
// bright badges mainly used in other projects (e.g. autoscout, dev portal, etc.)
bright: {
gray: {
bg: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } },
fg: { value: { _light: '{colors.gray.600}', _dark: '{colors.gray.200}' } },
},
green: {
bg: { value: { _light: '{colors.green.100}', _dark: '{colors.green.800}' } },
fg: { value: { _light: '{colors.green.600}', _dark: '{colors.green.200}' } },
},
red: {
bg: { value: { _light: '{colors.red.100}', _dark: '{colors.red.800}' } },
fg: { value: { _light: '{colors.red.600}', _dark: '{colors.red.200}' } },
},
blue: {
bg: { value: { _light: '{colors.blue.100}', _dark: '{colors.blue.800}' } },
fg: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.200}' } },
},
yellow: {
bg: { value: { _light: '{colors.yellow.100}', _dark: '{colors.yellow.800}' } },
fg: { value: { _light: '{colors.yellow.600}', _dark: '{colors.yellow.200}' } },
},
teal: {
bg: { value: { _light: '{colors.teal.100}', _dark: '{colors.teal.800}' } },
fg: { value: { _light: '{colors.teal.600}', _dark: '{colors.teal.200}' } },
},
cyan: {
bg: { value: { _light: '{colors.cyan.100}', _dark: '{colors.cyan.800}' } },
fg: { value: { _light: '{colors.cyan.600}', _dark: '{colors.cyan.200}' } },
},
orange: {
bg: { value: { _light: '{colors.orange.100}', _dark: '{colors.orange.600}' } },
fg: { value: { _light: '{colors.orange.600}', _dark: '{colors.orange.100}' } },
},
purple: {
bg: { value: { _light: '{colors.purple.50}', _dark: '{colors.purple.600}' } },
fg: { value: { _light: '{colors.purple.600}', _dark: '{colors.purple.50}' } },
},
pink: {
bg: { value: { _light: '{colors.pink.50}', _dark: '{colors.pink.600}' } },
fg: { value: { _light: '{colors.pink.600}', _dark: '{colors.pink.50}' } },
},
},
},
tag: {
root: {
+52
View File
@@ -57,6 +57,10 @@ export const recipe = defineRecipe({
bg: 'badge.cyan.bg',
color: 'badge.cyan.fg',
},
pink: {
bg: 'badge.pink.bg',
color: 'badge.pink.fg',
},
purple_alt: {
bg: 'badge.purple_alt.bg',
color: 'badge.purple_alt.fg',
@@ -65,6 +69,47 @@ export const recipe = defineRecipe({
bg: 'badge.blue_alt.bg',
color: 'badge.blue_alt.fg',
},
// bright badges mainly used in other projects (e.g. autoscout, dev portal, etc.)
bright_gray: {
bg: 'badge.bright.gray.bg',
color: 'badge.bright.gray.fg',
},
bright_green: {
bg: 'badge.bright.green.bg',
color: 'badge.bright.green.fg',
},
bright_red: {
bg: 'badge.bright.red.bg',
color: 'badge.bright.red.fg',
},
bright_blue: {
bg: 'badge.bright.blue.bg',
color: 'badge.bright.blue.fg',
},
bright_yellow: {
bg: 'badge.bright.yellow.bg',
color: 'badge.bright.yellow.fg',
},
bright_teal: {
bg: 'badge.bright.teal.bg',
color: 'badge.bright.teal.fg',
},
bright_cyan: {
bg: 'badge.bright.cyan.bg',
color: 'badge.bright.cyan.fg',
},
bright_orange: {
bg: 'badge.bright.orange.bg',
color: 'badge.bright.orange.fg',
},
bright_purple: {
bg: 'badge.bright.purple.bg',
color: 'badge.bright.purple.fg',
},
bright_pink: {
bg: 'badge.bright.pink.bg',
color: 'badge.bright.pink.fg',
},
},
size: {
sm: {
@@ -79,6 +124,13 @@ export const recipe = defineRecipe({
py: '0.5',
minH: '6',
},
lg: {
textStyle: 'sm',
px: '2',
py: '1',
minH: '7',
fontWeight: '600',
},
},
},
defaultVariants: {
+2
View File
@@ -3,6 +3,7 @@ export type MultichainProviderConfig = {
dapp_id?: string;
url_template: string;
logo: string;
promo?: boolean;
};
export type MultichainProviderConfigParsed = {
@@ -10,4 +11,5 @@ export type MultichainProviderConfigParsed = {
logoUrl: string;
urlTemplate: string;
dappId?: string;
promo?: boolean;
};
@@ -0,0 +1,57 @@
import React from 'react';
import type { Address } from 'types/api/address';
import config from 'configs/app';
import type { LinkProps } from 'toolkit/chakra/link';
import { Link } from 'toolkit/chakra/link';
const feature = config.features.multichainButton;
interface Props extends LinkProps {
addressData?: Address;
}
const AddressMultichainInfoButton = ({ addressData, ...rest }: Props) => {
if (!feature.isEnabled) {
return null;
}
const promotedProvider = feature.providers.find((provider) => provider.promo);
if (!promotedProvider) {
return null;
}
if (!addressData || (addressData.is_contract && addressData.proxy_type !== 'eip7702')) {
return null;
}
const url = (() => {
try {
const url = new URL(promotedProvider.urlTemplate.replace('{address}', addressData.hash));
url.searchParams.append('utm_source', 'blockscout');
url.searchParams.append('utm_medium', 'address');
return url.toString();
} catch (error) {}
return null;
})();
if (!url) {
return null;
}
return (
<Link
href={ url }
variant="underlaid"
external={ promotedProvider.dappId ? false : true }
flexShrink={ 0 }
{ ...rest }
>
Multichain info
</Link>
);
};
export default React.memo(AddressMultichainInfoButton);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

@@ -0,0 +1,119 @@
/* Animation source - https://codepen.io/ARS/pen/vEwEPP */
.promo {
position: relative;
z-index: 1;
&::after {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
margin: auto;
z-index: -1;
border-radius: 6px;
content: "";
background: linear-gradient(120deg, #e15eff, #fffb22, #e15eff);
background-size: 400% 400%;
clip-path: polygon(
0% 100%,
4px 100%,
4px 4px,
20px 4px,
20px 20px,
4px 20px,
4px 100%,
100% 100%,
100% 0%,
0% 0%
);
animation: gradient 3s ease-in-out 0.5s infinite, border 5s ease-in-out;
}
}
@keyframes gradient {
0% {
background-position: 14% 0%;
}
50% {
background-position: 87% 100%;
}
100% {
background-position: 14% 0%;
}
}
@keyframes border {
0% {
clip-path: polygon(
0% 100%,
4px 100%,
4px 100%,
4px 100%,
4px 100%,
4px 100%,
4px 100%,
4px 100%,
4px 100%,
0% 100%
);
}
5% {
clip-path: polygon(
0% 100%,
4px 100%,
4px 4px,
4px 4px,
4px 4px,
4px 4px,
4px 4px,
4px 4px,
4px 0%,
0% 0%
);
}
10% {
clip-path: polygon(
0% 100%,
4px 100%,
4px 4px,
20px 4px,
20px 4px,
20px 4px,
20px 4px,
20px 4px,
100% 0%,
0% 0%
);
}
15% {
clip-path: polygon(
0% 100%,
4px 100%,
4px 4px,
20px 4px,
20px 20px,
20px 20px,
20px 100%,
100% 100%,
100% 0%,
0% 0%
);
}
20%,
100% {
clip-path: polygon(
0% 100%,
4px 100%,
4px 4px,
20px 4px,
20px 20px,
4px 20px,
4px 100%,
100% 100%,
100% 0%,
0% 0%
);
}
}
+30 -18
View File
@@ -1,4 +1,4 @@
import { capitalize } from 'es-toolkit';
import { upperFirst } from 'es-toolkit';
import React from 'react';
import type { MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig';
@@ -8,36 +8,44 @@ import { route } from 'nextjs-routes';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
import TextSeparator from 'ui/shared/TextSeparator';
import styles from './AddressMultichainButton.module.css';
const TEMPLATE_ADDRESS = '{address}';
type Props = {
hasSingleProvider: boolean;
item: MultichainProviderConfigParsed;
addressHash: string;
onClick?: () => void;
isFirst: boolean;
isLast: boolean;
};
const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider }: Props) => {
const AddressMultichainButton = ({ item, addressHash, onClick, isFirst, isLast }: Props) => {
const isOnlyOne = isFirst && isLast;
const buttonIcon = (
<Image
src={ item.logoUrl }
alt={ item.name }
boxSize={ 5 }
mr={ hasSingleProvider ? { base: 1, lg: 2 } : 0 }
mr={ isOnlyOne ? { base: 1, lg: 2 } : 0 }
borderRadius="4px"
overflow="hidden"
/>
);
const buttonContent = hasSingleProvider ? (
const name = upperFirst(item.name.replaceAll('_', ' '));
const buttonContent = isOnlyOne ? (
<>
{ buttonIcon }
{ capitalize(item.name) }
{ name }
</>
) : (
<Tooltip content={ capitalize(item.name) }>{ buttonIcon }</Tooltip>
<Tooltip content={ name }>{ buttonIcon }</Tooltip>
);
try {
@@ -49,17 +57,21 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider
const isExternal = typeof dappId !== 'string';
return (
<Link
external={ isExternal }
href={ isExternal ? portfolioUrl.toString() : route({ pathname: '/apps/[id]', query: { id: dappId, url: portfolioUrl.toString() } }) }
variant={ hasSingleProvider ? 'underlaid' : undefined }
textStyle="sm"
fontWeight="medium"
onClick={ onClick }
noIcon={ !hasSingleProvider }
>
{ buttonContent }
</Link>
<>
<Link
className={ item.promo ? styles.promo : undefined }
external={ isExternal }
href={ isExternal ? portfolioUrl.toString() : route({ pathname: '/apps/[id]', query: { id: dappId, url: portfolioUrl.toString() } }) }
variant={ isOnlyOne ? 'underlaid' : undefined }
textStyle="sm"
fontWeight="medium"
onClick={ onClick }
noIcon={ !isOnlyOne }
>
{ buttonContent }
</Link>
{ item.promo && isFirst && !isLast && <TextSeparator mx={ 0 }/> }
</>
);
} catch (error) {}
+5 -2
View File
@@ -9,6 +9,7 @@ import AddressNetWorth from './AddressNetWorth';
const ADDRESS_HASH = addressMock.hash;
const ICON_URL = 'https://localhost:3000/my-icon.png';
const ICON_URL_PROMO = 'https://localhost:3000/my-icon-promo.png';
test.beforeEach(async({ mockApiResponse }) => {
await mockApiResponse('general:address_tokens', tokensMock.erc20List, { pathParams: { hash: ADDRESS_HASH }, queryParams: { type: 'ERC-20' } });
@@ -47,14 +48,16 @@ test('with single multichain button external', async({ render, mockEnvs, mockAss
await expect(component).toHaveScreenshot();
});
test('with two multichain button external', async({ render, mockEnvs, mockAssetResponse }) => {
test('with two multichain button and promo', async({ render, mockEnvs, mockAssetResponse }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG', `[
{"name": "duck", "url_template": "https://duck.url/{address}", "logo": "${ ICON_URL }"},
{"name": "duck2", "url_template": "https://duck.url/{address}", "logo": "${ ICON_URL }"}
{"name": "duck2", "url_template": "https://duck.url/{address}", "logo": "${ ICON_URL }"},
{"name": "duck3", "url_template": "https://duck.url/{address}", "logo": "${ ICON_URL_PROMO }", "promo": true}
]` ],
]);
await mockAssetResponse(ICON_URL, './playwright/mocks/image_svg.svg');
await mockAssetResponse(ICON_URL_PROMO, './playwright/mocks/image_s.jpg');
const component = await render(<AddressNetWorth addressData={ addressMock.eoa } addressHash={ ADDRESS_HASH }/>);
+16 -14
View File
@@ -1,4 +1,4 @@
import { Text, Flex } from '@chakra-ui/react';
import { Text, HStack } from '@chakra-ui/react';
import React from 'react';
import type { Address } from 'types/api/address';
@@ -47,24 +47,26 @@ const AddressNetWorth = ({ addressData, isLoading, addressHash }: Props) => {
if (multichainFeature.isEnabled && (!addressData?.is_contract || isEip7702)) {
const { providers } = multichainFeature;
const hasSingleProvider = providers.length === 1;
multichainItems = (
<>
<TextSeparator/>
<Flex alignItems="center" columnGap={ 2 }>
<HStack columnGap={ 2 }>
<Text>Multichain</Text>
{ providers.map((item) => (
<AddressMultichainButton
key={ item.name }
item={ item }
addressHash={ addressHash }
onClick={ onMultichainClick }
hasSingleProvider={ hasSingleProvider }
/>
))
}
</Flex>
<HStack gap={{ base: 2, lg: 3 }}>
{ providers.map((item, index) => (
<AddressMultichainButton
key={ item.name }
item={ item }
addressHash={ addressHash }
onClick={ onMultichainClick }
isFirst={ index === 0 }
isLast={ index === providers.length - 1 }
/>
))
}
</HStack>
</HStack>
</>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

+1 -13
View File
@@ -5,7 +5,6 @@ import type { FormattedData } from './types';
import * as mixpanel from 'lib/mixpanel/index';
import { Button } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { space } from 'toolkit/utils/htmlEntities';
import IconSvg from 'ui/shared/IconSvg';
@@ -39,6 +38,7 @@ const TokenSelectButton = ({ isOpen, isLoading, data, ...rest }: Props, ref: Rea
onClick={ handleClick }
gap={ 0 }
aria-label="Token select"
loadingSkeleton={ isLoading && !isOpen }
{ ...rest }
>
<IconSvg name="tokens" boxSize={ 4 } mr={ 2 }/>
@@ -56,18 +56,6 @@ const TokenSelectButton = ({ isOpen, isLoading, data, ...rest }: Props, ref: Rea
</chakra.span>
<IconSvg name="arrows/east-mini" transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 3 }/>
</Button>
{ isLoading && !isOpen && (
<Skeleton
loading
h="100%"
w="100%"
position="absolute"
top={ 0 }
left={ 0 }
bgColor="bg.primary"
borderRadius="base"
/>
) }
</Box>
);
};
+1 -1
View File
@@ -41,7 +41,7 @@ const ERC20TokensTableItem = ({
}, [ chainValues ]);
return (
<TableRow role="group" >
<TableRow className="group" >
<TableCell verticalAlign="middle">
<HStack gap={ 2 }>
<TokenEntity
+16 -14
View File
@@ -81,20 +81,22 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
isLoading={ isLoading }
mode="compact"
/>
<Flex flexDir="column" rowGap={ 3 }>
{ !config.UI.views.tx.hiddenFields?.value && (
<Skeleton loading={ isLoading }>
<Text as="span" whiteSpace="pre">Value </Text>
<Text as="span" color="text.secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether }</Text>
</Skeleton>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton loading={ isLoading } display="flex" whiteSpace="pre">
<Text as="span">Fee </Text>
<TxFee tx={ tx } accuracy={ 5 } color="text.secondary"/>
</Skeleton>
) }
</Flex>
{ !(config.UI.views.tx.hiddenFields?.value && config.UI.views.tx.hiddenFields?.tx_fee) ? (
<Flex flexDir="column" rowGap={ 3 }>
{ !config.UI.views.tx.hiddenFields?.value && (
<Skeleton loading={ isLoading }>
<Text as="span" whiteSpace="pre">Value </Text>
<Text as="span" color="text.secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether }</Text>
</Skeleton>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton loading={ isLoading } display="flex" whiteSpace="pre">
<Text as="span">Fee </Text>
<TxFee tx={ tx } accuracy={ 5 } color="text.secondary"/>
</Skeleton>
) }
</Flex>
) : null }
</Grid>
);
};
+17 -13
View File
@@ -3,6 +3,7 @@ import {
Flex,
HStack,
Text,
VStack,
} from '@chakra-ui/react';
import React from 'react';
@@ -73,20 +74,23 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
to={ dataTo }
isLoading={ isLoading }
fontWeight="500"
mb={ 3 }
/>
{ !config.UI.views.tx.hiddenFields?.value && (
<Skeleton loading={ isLoading } mb={ 2 } w="fit-content">
<Text as="span">Value </Text>
<Text as="span" color="text.secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether }</Text>
</Skeleton>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton loading={ isLoading } w="fit-content" display="flex" whiteSpace="pre">
<Text as="span">Fee </Text>
<TxFee tx={ tx } accuracy={ 5 } color="text.secondary"/>
</Skeleton>
) }
{ !(config.UI.views.tx.hiddenFields?.value && config.UI.views.tx.hiddenFields?.tx_fee) ? (
<VStack rowGap={ 2 } mt={ 3 } alignItems="flex-start">
{ !config.UI.views.tx.hiddenFields?.value && (
<Skeleton loading={ isLoading } w="fit-content">
<Text as="span">Value </Text>
<Text as="span" color="text.secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether }</Text>
</Skeleton>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton loading={ isLoading } w="fit-content" display="flex" whiteSpace="pre">
<Text as="span">Fee </Text>
<TxFee tx={ tx } accuracy={ 5 } color="text.secondary"/>
</Skeleton>
) }
</VStack>
) : null }
</Box>
);
};
@@ -137,7 +137,7 @@ const OpSuperchainAddressDetails = ({ data, addressHash, isLoading }: Props) =>
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue whiteSpace="pre-wrap">
<Skeleton loading={ isLoading }>
Local <Link href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'txs_local' } }) }>view by chain</Link>
<Link href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'txs_local' } }) }>View by chain</Link>
</Skeleton>
</DetailedInfo.ItemValue>
@@ -149,7 +149,7 @@ const OpSuperchainAddressDetails = ({ data, addressHash, isLoading }: Props) =>
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue whiteSpace="pre-wrap">
<Skeleton loading={ isLoading }>
Local <Link href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'token_transfers_local' } }) }>view by chain</Link>
<Link href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'token_transfers_local' } }) }>View by chain</Link>
</Skeleton>
</DetailedInfo.ItemValue>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

@@ -50,13 +50,13 @@ const OpSuperchainTokenSelect = ({ isLoading, isError, data }: Props) => {
href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'tokens' } }) }
asChild
scroll={ false }
loading={ isLoading }
>
<IconButton
aria-label="Show all tokens"
variant="icon_secondary"
size="md"
onClick={ handleIconButtonClick }
loadingSkeleton={ isLoading }
>
<IconSvg name="wallet"/>
</IconButton>
@@ -1,17 +1,15 @@
import { Box, HStack } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { ClusterChainConfig } from 'types/multichain';
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 BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
interface Props {
@@ -50,20 +48,13 @@ const ChainLatestBlockInfo = ({ chainData }: Props) => {
}
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: chainData.slug,
height_or_hash: blocksQuery.data[0].height.toString(),
},
}) }
>
{ blocksQuery.data[0].height }
</Link>
<Box display="grid" gridTemplateColumns="repeat(3, auto)" gap={ 2 }>
<Box color="text.secondary" whiteSpace="nowrap">Latest block</Box>
<BlockEntity
number={ blocksQuery.data[0].height }
isLoading={ blocksQuery.isPlaceholderData }
noIcon
/>
<TimeWithTooltip
timestamp={ blocksQuery.data[0].timestamp }
enableIncrement={ !blocksQuery.isPlaceholderData }
@@ -72,7 +63,7 @@ const ChainLatestBlockInfo = ({ chainData }: Props) => {
flexShrink={ 0 }
timeFormat="relative"
/>
</HStack>
</Box>
);
};
+1 -1
View File
@@ -78,7 +78,7 @@ const ChainWidget = ({ data }: Props) => {
</HStack>
</HStack>
<Heading my={ 3 } textStyle="heading.md ">{ data.name }</Heading>
<RollupStageBadge chainConfig={ data.app_config } variant="filled" mb={ 2.5 }/>
<RollupStageBadge chainConfig={ data.app_config } mb={ 2.5 }/>
<VStack gap={ 2 } alignItems="flex-start" fontWeight={ 500 }>
<HStack gap={ 2 }>
<Box color="text.secondary">Chain ID</Box>
@@ -30,8 +30,8 @@ const LatestTxsLocal = () => {
const query = useApiQuery('general:homepage_txs', {
chain,
queryOptions: {
placeholderData: Array(5).fill(TX),
select: (data) => data.slice(0, 5),
placeholderData: Array(3).fill(TX),
select: (data) => data.slice(0, 3),
},
});
@@ -47,7 +47,7 @@ const OpSuperchainTokenTransfersLocal = ({ query, typeFilter, onTokenTypesChange
<Box hideFrom="lg">
{ query.data?.items.map((item, index) => (
<TokenTransfersListItem
key={ item.transaction_hash + item.log_index + (query.isPlaceholderData ? index : '') }
key={ item.transaction_hash + item.log_index + (query.isPlaceholderData ? index : '') + (chainData ? chainData.id : '') }
isLoading={ query.isPlaceholderData }
item={ item }
chainData={ chainData }
+2
View File
@@ -34,6 +34,7 @@ import AddressEpochRewards from 'ui/address/AddressEpochRewards';
import AddressInternalTxs from 'ui/address/AddressInternalTxs';
import AddressLogs from 'ui/address/AddressLogs';
import AddressMud from 'ui/address/AddressMud';
import AddressMultichainInfoButton from 'ui/address/AddressMultichainInfoButton';
import AddressTokens from 'ui/address/AddressTokens';
import AddressTokenTransfers from 'ui/address/AddressTokenTransfers';
import AddressTxs from 'ui/address/AddressTxs';
@@ -449,6 +450,7 @@ const AddressPageContent = () => {
<AddressQrCode hash={ addressQuery.data?.filecoin?.robust ?? checkSummedHash } isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/>
<HStack ml="auto" gap={ 2 }/>
<AddressMultichainInfoButton loading={ isLoading } addressData={ addressQuery.data }/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
<SolidityscanReport hash={ hash }/> }
{ !isLoading && nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled &&
@@ -29,7 +29,7 @@ const AppActionButton = ({ data, className, txHash, source }: Props) => {
}
}, [ source, appID, actionURL ]);
if ((!appID && !appMarketplaceURL) || (!appActionButtonText && !appLogoURL)) {
if ((!appID && !actionURL) || (!appActionButtonText && !appLogoURL)) {
return null;
}
@@ -50,13 +50,11 @@ const AppActionButton = ({ data, className, txHash, source }: Props) => {
</>
);
const isExternal = !appID;
return (
<Link
className={ className }
href={ isExternal ? actionURL : route({ pathname: '/apps/[id]', query: { id: appID, action: 'connect', ...(actionURL ? { url: actionURL } : {}) } }) }
external={ isExternal }
href={ actionURL ?? (appID ? route({ pathname: '/apps/[id]', query: { id: appID, action: 'connect' } }) : undefined) }
external={ Boolean(actionURL) }
onClick={ handleClick }
variant="underlaid"
iconColor={ textColor }
+38 -2
View File
@@ -40,12 +40,45 @@ const BadgeShowcase = () => {
<Sample label="colorPalette: cyan">
<Badge colorPalette="cyan">Internal txn</Badge>
</Sample>
<Sample label="colorPalette: pink">
<Badge colorPalette="pink">Content</Badge>
</Sample>
<Sample label="colorPalette: purple_alt">
<Badge colorPalette="purple_alt">read</Badge>
</Sample>
<Sample label="colorPalette: blue_alt">
<Badge colorPalette="blue_alt">write</Badge>
</Sample>
<Sample label="colorPalette: bright_gray">
<Badge colorPalette="bright_gray">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_green">
<Badge colorPalette="bright_green">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_red">
<Badge colorPalette="bright_red">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_blue">
<Badge colorPalette="bright_blue">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_yellow">
<Badge colorPalette="bright_yellow">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_teal">
<Badge colorPalette="bright_teal">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_cyan">
<Badge colorPalette="bright_cyan">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_orange">
<Badge colorPalette="bright_orange">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_purple">
<Badge colorPalette="bright_purple">Content</Badge>
</Sample>
<Sample label="colorPalette: bright_pink">
<Badge colorPalette="bright_pink">Content</Badge>
</Sample>
</SamplesStack>
</Section>
<Section>
@@ -59,11 +92,14 @@ const BadgeShowcase = () => {
<Section>
<SectionHeader>Size</SectionHeader>
<SamplesStack>
<Sample label="size: sm">
<Badge size="sm">Content</Badge>
</Sample>
<Sample label="size: md">
<Badge size="md">Content</Badge>
</Sample>
<Sample label="size: sm">
<Badge size="sm">Content</Badge>
<Sample label="size: lg">
<Badge size="lg">Content</Badge>
</Sample>
</SamplesStack>
</Section>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 116 KiB

+1 -1
View File
@@ -113,7 +113,7 @@ const Footer = () => {
_empty={{ display: 'none' }}
>
{ !config.UI.indexingAlert.intTxs.isHidden && <IntTxsIndexingStatus/> }
<NetworkAddToWallet source="Footer"/>
{ !config.features.opSuperchain.isEnabled && <NetworkAddToWallet source="Footer"/> }
</Flex>
);
}, []);
+2 -11
View File
@@ -8,10 +8,9 @@ import { Tooltip } from 'toolkit/chakra/tooltip';
interface Props extends ImageProps {
chainConfig?: typeof config;
variant?: 'outline' | 'filled';
}
const RollupStageBadge = ({ chainConfig = config, variant = 'outline', ...props }: Props) => {
const RollupStageBadge = ({ chainConfig = config, ...props }: Props) => {
const feature = chainConfig.features.rollup;
@@ -31,18 +30,10 @@ const RollupStageBadge = ({ chainConfig = config, variant = 'outline', ...props
</>
);
const src = (() => {
if (variant === 'filled') {
return feature.stageIndex === '1' ? '/static/labels/stage-1-filled.svg' : '/static/labels/stage-2-filled.svg';
}
return feature.stageIndex === '1' ? '/static/labels/stage-1.svg' : '/static/labels/stage-2.svg';
})();
return (
<Tooltip content={ tooltipContent } interactive>
<Image
src={ src }
src={ feature.stageIndex === '1' ? '/static/labels/stage-1.svg' : '/static/labels/stage-2.svg' }
h="14px"
w="42px"
{ ...props }
+1
View File
@@ -27,6 +27,7 @@ const TopBar = () => {
web3.data?.wallet &&
config.chain.rpcUrls.length &&
config.features.web3Wallet.isEnabled &&
!config.features.opSuperchain.isEnabled &&
!isMobile,
);
const hasDeFiDropdown = Boolean(config.features.deFiDropdown.isEnabled);
+2 -1
View File
@@ -56,7 +56,8 @@ test('base view +@dark-mode', async({ render, page }) => {
test.describe('action button', () => {
test.beforeEach(async({ mockApiResponse, mockAssetResponse }) => {
const metadataResponse = generateAddressMetadataResponse(protocolTagWithMeta);
const meta = { ...protocolTagWithMeta.meta, appMarketplaceURL: undefined };
const metadataResponse = generateAddressMetadataResponse({ ...protocolTagWithMeta, meta });
await mockApiResponse('metadata:info', metadataResponse, { queryParams: addressMetadataQueryParams });
await mockAssetResponse(protocolTagWithMeta?.meta?.appLogoURL as string, './playwright/mocks/image_s.jpg');
});
+1 -1
View File
@@ -36,7 +36,7 @@ const TokenTransferTable = ({ items, top, isLoading, chainData }: Props) => {
<TableBody>
{ items?.map((item, index) => (
<TokenTransferTableItem
key={ item.transaction_hash + item.log_index + (isLoading ? index : '') }
key={ item.transaction_hash + item.log_index + (isLoading ? index : '') + (chainData ? chainData.id : '') }
item={ item }
isLoading={ isLoading }
chainData={ chainData }
+13 -9
View File
@@ -33,15 +33,19 @@ const Tokens = ({ query, onSortChange, sort, actionBar, description, hasActiveFi
<>
<Box hideFrom="lg">
{ description }
{ data.items.map((item, index) => (
<TokensListItem
key={ item.address_hash + (isPlaceholderData ? index : '') }
token={ item }
index={ index }
page={ pagination.page }
isLoading={ isPlaceholderData }
/>
)) }
{ data.items.map((item, index) => {
const chainIds = 'chain_infos' in item ? Object.keys(item.chain_infos).join(',') : undefined;
return (
<TokensListItem
key={ item.address_hash + (isPlaceholderData ? index : '') + (chainIds ? chainIds : '') }
token={ item }
index={ index }
page={ pagination.page }
isLoading={ isPlaceholderData }
/>
);
}) }
</Box>
<Box hideBelow="lg">
{ description }
+13 -3
View File
@@ -92,9 +92,19 @@ const TokensTable = ({ items, page, isLoading, sorting, setSorting, top }: Props
</TableRow>
</TableHeaderSticky>
<TableBody>
{ items.map((item, index) => (
<TokensTableItem key={ item.address_hash + (isLoading ? index : '') } token={ item } index={ index } page={ page } isLoading={ isLoading }/>
)) }
{ items.map((item, index) => {
const chainIds = 'chain_infos' in item ? Object.keys(item.chain_infos).join(',') : undefined;
return (
<TokensTableItem
key={ item.address_hash + (isLoading ? index : '') + (chainIds ? chainIds : '') }
token={ item }
index={ index }
page={ page }
isLoading={ isLoading }
/>
);
}) }
</TableBody>
</TableRoot>
);
+2 -2
View File
@@ -58,7 +58,8 @@ test.describe('blockscout provider', () => {
});
test('with interpretation and action button +@mobile +@dark-mode', async({ render, mockApiResponse, mockAssetResponse }) => {
const metadataResponse = generateAddressMetadataResponse(protocolTagWithMeta);
const meta = { ...protocolTagWithMeta.meta, appMarketplaceURL: undefined };
const metadataResponse = generateAddressMetadataResponse({ ...protocolTagWithMeta, meta });
await mockApiResponse('metadata:info', metadataResponse, { queryParams: addressMetadataQueryParams });
await mockAssetResponse(protocolTagWithMeta?.meta?.appLogoURL as string, './playwright/mocks/image_s.jpg');
await mockApiResponse('general:tx_interpretation', txInterpretation, { pathParams: { hash } });
@@ -100,7 +101,6 @@ test.describe('blockscout provider', () => {
test('with interpretation and view all link, and action button (external link) +@mobile', async({
render, mockApiResponse, mockAssetResponse,
}) => {
delete protocolTagWithMeta?.meta?.appID;
const metadataResponse = generateAddressMetadataResponse(protocolTagWithMeta);
await mockApiResponse('metadata:info', metadataResponse, { queryParams: addressMetadataQueryParams });
await mockAssetResponse(protocolTagWithMeta?.meta?.appLogoURL as string, './playwright/mocks/image_s.jpg');
+16 -8
View File
@@ -58,6 +58,14 @@ const TxsTable = ({
const TableHeaderComponent = stickyHeader ? TableHeaderSticky : TableHeader;
const columnNum = [
showBlockInfo,
true,
!config.UI.views.tx.hiddenFields?.value,
!config.UI.views.tx.hiddenFields?.tx_fee,
].filter(Boolean).length;
const baseWidth = `${ 100 / columnNum }%`;
return (
<AddressHighlightProvider>
<TableRoot minWidth="1000px">
@@ -70,11 +78,11 @@ const TxsTable = ({
<TimeFormatToggle/>
</TableColumnHeader>
<TableColumnHeader width="160px">Type</TableColumnHeader>
<TableColumnHeader width="20%">Method</TableColumnHeader>
<TableColumnHeader width={ baseWidth }>Method</TableColumnHeader>
{ showBlockInfo && (
onSortToggle ? (
<TableColumnHeaderSortable
width="18%"
width={ baseWidth }
sortField="block_number"
sortValue={ sort }
onSortToggle={ onSortToggle }
@@ -82,14 +90,14 @@ const TxsTable = ({
Block
</TableColumnHeaderSortable>
) : (
<TableColumnHeader width="18%">Block</TableColumnHeader>
<TableColumnHeader width={ baseWidth }>Block</TableColumnHeader>
)
) }
<TableColumnHeader width="224px">From/To</TableColumnHeader>
<TableColumnHeader width={ columnNum <= 2 ? baseWidth : '224px' }>From/To</TableColumnHeader>
{ !config.UI.views.tx.hiddenFields?.value && (
onSortToggle ? (
<TableColumnHeaderSortable
width="20%"
width={ baseWidth }
isNumeric
sortField="value"
sortValue={ sort }
@@ -98,13 +106,13 @@ const TxsTable = ({
{ `Value ${ currencyUnits.ether }` }
</TableColumnHeaderSortable>
) : (
<TableColumnHeader width="20%" isNumeric>Value</TableColumnHeader>
<TableColumnHeader width={ baseWidth } isNumeric>Value</TableColumnHeader>
)
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
onSortToggle ? (
<TableColumnHeaderSortable
width="20%"
width={ baseWidth }
isNumeric
pr={ 5 }
sortField="fee"
@@ -114,7 +122,7 @@ const TxsTable = ({
{ `Fee${ feeCurrency }` }
</TableColumnHeaderSortable>
) : (
<TableColumnHeader width="20%" isNumeric pr={ 5 }>Fee</TableColumnHeader>
<TableColumnHeader width={ baseWidth } isNumeric pr={ 5 }>Fee</TableColumnHeader>
)
) }
</TableRow>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

+63 -63
View File
@@ -5103,10 +5103,10 @@
dependencies:
webpack-bundle-analyzer "4.10.1"
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.5.2.tgz#0c6b959313cd6e71afb69bf0deb417237f1d2f8a"
integrity sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.5.7.tgz#4168db34ae3bc9fd9ad3b951d327f4cfc38d4362"
integrity sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==
"@next/[email protected]":
version "15.0.3"
@@ -5115,45 +5115,45 @@
dependencies:
fast-glob "3.3.1"
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz#f69713326fc08f2eff3726fe19165cdb429d67c7"
integrity sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz#f0c9ccfec2cd87cbd4b241ce4c779a7017aed958"
integrity sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz#560a9da4126bae75cbbd6899646ad7a2e4fdcc9b"
integrity sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz#18009e9fcffc5c0687cc9db24182ddeac56280d9"
integrity sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz#80b2be276e775e5a9286369ae54e536b0cdf8c3a"
integrity sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz#fe7c7e08264cf522d4e524299f6d3e63d68d579a"
integrity sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz#68cf676301755fd99aca11a7ebdb5eae88d7c2e4"
integrity sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz#94228fe293475ec34a5a54284e1056876f43a3cf"
integrity sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz#209d9a79d0f2333544f863b0daca3f7e29f2eaff"
integrity sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz#078c71201dfe7fcfb8fa6dc92aae6c94bc011cdc"
integrity sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz#d4ad1cfb5e99e51db669fe2145710c1abeadbd7f"
integrity sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz#72947f5357f9226292353e0bb775643da3c7a182"
integrity sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz#070e10e370a5447a198c2db100389646aca2c496"
integrity sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz#397b912cd51c6a80e32b9c0507ecd82514353941"
integrity sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==
"@next/[email protected].2":
version "15.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz#9237d40b82eaf2efc88baeba15b784d4126caf4a"
integrity sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==
"@next/[email protected].7":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz#e02b543d9dc6c1631d4ac239cb1177245dfedfe4"
integrity sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==
"@noble/[email protected]":
version "1.2.1"
@@ -12682,13 +12682,13 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
[email protected].0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.3.0.tgz#785d46774552dbc2a29f4196b6f39daa53689cd6"
integrity sha512-vHiDO9jXG4Q80ZZbtqiHrDXEYbO1q3nNGYnF5nbUpe7lrYiCHvXK/NUKXC6sHt1zp8tSfD95fmgY/6ZDCrgfpA==
[email protected].2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.3.2.tgz#a338a69a23ca1f4abe45566659c733afa1db0f13"
integrity sha512-YZN/GTCUKm+gTi9FkFeRxmNqvh3FY3pQS+JFUuCcK7aTFmfPzohJFG1ILZafZbI44V2mJAc63PEBfdiNbwJYiQ==
dependencies:
react "19.1.0"
react-dom "19.1.0"
react "19.1.2"
react-dom "19.1.2"
viem "^1.20.3"
data-uri-to-buffer@^4.0.0:
@@ -17602,25 +17602,25 @@ [email protected]:
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.4.tgz#ce6f68a4af543821bbc4755b59c0d3ced55c2d13"
integrity sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==
[email protected].2:
version "15.5.2"
resolved "https://registry.yarnpkg.com/next/-/next-15.5.2.tgz#5e50102443fb0328a9dfcac2d82465c7bac93693"
integrity sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==
[email protected].7:
version "15.5.7"
resolved "https://registry.yarnpkg.com/next/-/next-15.5.7.tgz#4507700b2bbcaf2c9fb7a9ad25c0dac2ba4a9a75"
integrity sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==
dependencies:
"@next/env" "15.5.2"
"@next/env" "15.5.7"
"@swc/helpers" "0.5.15"
caniuse-lite "^1.0.30001579"
postcss "8.4.31"
styled-jsx "5.1.6"
optionalDependencies:
"@next/swc-darwin-arm64" "15.5.2"
"@next/swc-darwin-x64" "15.5.2"
"@next/swc-linux-arm64-gnu" "15.5.2"
"@next/swc-linux-arm64-musl" "15.5.2"
"@next/swc-linux-x64-gnu" "15.5.2"
"@next/swc-linux-x64-musl" "15.5.2"
"@next/swc-win32-arm64-msvc" "15.5.2"
"@next/swc-win32-x64-msvc" "15.5.2"
"@next/swc-darwin-arm64" "15.5.7"
"@next/swc-darwin-x64" "15.5.7"
"@next/swc-linux-arm64-gnu" "15.5.7"
"@next/swc-linux-arm64-musl" "15.5.7"
"@next/swc-linux-x64-gnu" "15.5.7"
"@next/swc-linux-x64-musl" "15.5.7"
"@next/swc-win32-arm64-msvc" "15.5.7"
"@next/swc-win32-x64-msvc" "15.5.7"
sharp "^0.34.3"
nextjs-routes@^1.0.8:
@@ -19079,10 +19079,10 @@ react-device-detect@^2.2.3:
dependencies:
ua-parser-js "^1.0.33"
[email protected].0:
version "19.1.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623"
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
[email protected].2:
version "19.1.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.2.tgz#18ce6b14f682f4871a69cc6b2568d42af2cd5a33"
integrity sha512-dEoydsCp50i7kS1xHOmPXq4zQYoGWedUsvqv9H6zdif2r7yLHygyfP9qou71TulRN0d6ng9EbRVsQhSqfUc19g==
dependencies:
scheduler "^0.26.0"
@@ -19319,10 +19319,10 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5:
loose-envify "^1.4.0"
prop-types "^15.6.2"
[email protected].0:
version "19.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
[email protected].2:
version "19.1.2"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.2.tgz#fd75c4f684b52fe45101381535c58486f4407545"
integrity sha512-MdWVitvLbQULD+4DP8GYjZUrepGW7d+GQkNVqJEzNxE+e9WIa4egVFE/RDfVb1u9u/Jw7dNMmPB4IqxzbFYJ0w==
[email protected], readable-stream@^3.4.0, readable-stream@^3.6.2:
version "3.6.2"