Fixes for release v2.4.0 (#3079)

* fix links in approvals list

* pass check-summed hash to safe API call

* make tx hash optional for token transfers

* fix multiple public tag submission

* update screenshots

* use DataListDisplay for revoke

* force to send credential only with general API requests

* prefer formatted date label over date default formatting when saving chart data as csv

* add abort failed status for zeta chain CCTX

* update specify sdk

* rename ENV for native token address

* add ENV to hide native coin price in topbar

* rename routes for name services

* fix chart csv export file

* fix tests

---------

Co-authored-by: Max Alekseenko <nnwen0609@gmail.com>
Co-authored-by: isstuev <isstuev@gmail.com>
This commit is contained in:
tom goriunov
2025-10-29 15:54:06 +01:00
committed by GitHub
co-authored by Max Alekseenko isstuev
parent 1c534597bc
commit 0eadf590e7
100 changed files with 418 additions and 357 deletions
+1 -2
View File
@@ -4,13 +4,12 @@ import { getEnvValue } from '../utils';
const title = 'Celo chain';
const config: Feature<{ nativeTokenAddress?: string }> = (() => {
const config: Feature<{ }> = (() => {
if (getEnvValue('NEXT_PUBLIC_CELO_ENABLED') === 'true') {
return Object.freeze({
title,
isEnabled: true,
nativeTokenAddress: getEnvValue('NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS'),
});
}
-26
View File
@@ -1,26 +0,0 @@
import type { Feature } from './types';
import apis from '../apis';
import { getEnvValue } from '../utils';
const title = 'Clusters universal name service';
const config: Feature<{ cdnUrl: string }> = (() => {
const cdnUrl = getEnvValue('NEXT_PUBLIC_CLUSTERS_CDN_URL') || 'https://cdn.clusters.xyz';
if (apis.clusters) {
return Object.freeze({
title,
isEnabled: true,
cdnUrl,
});
}
return Object.freeze({
title,
isEnabled: false,
cdnUrl,
});
})();
export default config;
+1 -2
View File
@@ -10,7 +10,6 @@ export { default as beaconChain } from './beaconChain';
export { default as bridgedTokens } from './bridgedTokens';
export { default as blockchainInteraction } from './blockchainInteraction';
export { default as celo } from './celo';
export { default as clusters } from './clusters';
export { default as csvExport } from './csvExport';
export { default as dataAvailability } from './dataAvailability';
export { default as deFiDropdown } from './deFiDropdown';
@@ -29,7 +28,7 @@ export { default as metasuites } from './metasuites';
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 nameServices } from './nameServices';
export { default as opSuperchain } from './opSuperchain';
export { default as pools } from './pools';
export { default as publicTagsSubmission } from './publicTagsSubmission';
-21
View File
@@ -1,21 +0,0 @@
import type { Feature } from './types';
import apis from '../apis';
const title = 'Name service integration';
const config: Feature<{}> = (() => {
if (apis.bens) {
return Object.freeze({
title,
isEnabled: true,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
+29
View File
@@ -0,0 +1,29 @@
import type { Feature } from './types';
import apis from '../apis';
import { getEnvValue } from '../utils';
const title = 'Name services integration';
const config: Feature<{ ens: { isEnabled: boolean }; clusters: { isEnabled: boolean; cdnUrl: string } }> = (() => {
if (apis.bens || apis.clusters) {
return Object.freeze({
title,
isEnabled: true,
ens: {
isEnabled: apis.bens ? true : false,
},
clusters: {
isEnabled: apis.clusters ? true : false,
cdnUrl: getEnvValue('NEXT_PUBLIC_CLUSTERS_CDN_URL') || 'https://cdn.clusters.xyz',
},
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
+3
View File
@@ -99,6 +99,9 @@ const UI = Object.freeze({
body: parseEnvJson<FontFamily>(getEnvValue('NEXT_PUBLIC_FONT_FAMILY_BODY')),
},
maxContentWidth: getEnvValue('NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED') === 'false' ? false : true,
nativeCoinPrice: {
isHidden: getEnvValue('NEXT_PUBLIC_HIDE_NATIVE_COIN_PRICE') === 'true' ? true : false,
},
});
export default UI;
+1
View File
@@ -82,6 +82,7 @@ const config = Object.freeze({
bech32Prefix,
},
hiddenViews,
nativeTokenAddress: getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS'),
solidityscanEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED') === 'true',
extraVerificationMethods,
languageFilters,
+1 -1
View File
@@ -9,7 +9,7 @@ NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS=0x471EcE3750Da237f93B8E339c536989b8978a438
NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS=0x471EcE3750Da237f93B8E339c536989b8978a438
# Instance ENVs
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['talentprotocol', 'efp', 'webacy', 'deepdao', 'humanpassport', 'bankless', 'blockscoutbadges']
+6 -10
View File
@@ -399,16 +399,6 @@ const rollupSchema = yup
.object()
.shape({
NEXT_PUBLIC_CELO_ENABLED: yup.boolean(),
NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS: yup
.string()
.min(42)
.max(42)
.matches(regexp.HEX_REGEXP_WITH_0X)
.when('NEXT_PUBLIC_CELO_ENABLED', {
is: (value: boolean) => value,
then: (schema) => schema,
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS can only be used if NEXT_PUBLIC_CELO_ENABLED is set to \'true\''),
}),
});
const megaEthSchema = yup
@@ -981,6 +971,11 @@ const schema = yup
then: (schema) => schema.required().min(1).max(83),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX is required if NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT contains "bech32"'),
}),
NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS: yup
.string()
.min(42)
.max(42)
.matches(regexp.HEX_REGEXP_WITH_0X),
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: yup
.array()
@@ -1043,6 +1038,7 @@ const schema = yup
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: yup.boolean(),
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS: yup.boolean(),
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(),
NEXT_PUBLIC_HIDE_NATIVE_COIN_PRICE: yup.boolean(),
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(),
NEXT_PUBLIC_COLOR_THEME_DEFAULT: yup.string().oneOf(COLOR_THEME_IDS),
NEXT_PUBLIC_COLOR_THEME_OVERRIDES: yup.object().transform(replaceQuotes).json(),
@@ -32,6 +32,7 @@ NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c18099
NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED=false
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false
NEXT_PUBLIC_HIDE_NATIVE_COIN_PRICE=false
NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED=false
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','current_epoch']
@@ -68,6 +69,7 @@ NEXT_PUBLIC_USE_NEXT_JS_PROXY=false
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16']
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts']
NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS=0x471EcE3750Da237f93B8E339c536989b8978a438
NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=['solidity-hardhat','solidity-foundry']
NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS=['solidity','vyper','yul','scilla','geas','stylus_rust']
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward']
+1 -2
View File
@@ -1,2 +1 @@
NEXT_PUBLIC_CELO_ENABLED=true
NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS=0x471EcE3750Da237f93B8E339c536989b8978a438
NEXT_PUBLIC_CELO_ENABLED=true
+2 -1
View File
@@ -279,6 +279,7 @@ Settings for meta tags, OG tags and SEO
| NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT | `Array<"base16" \| "bech32">` | Displayed address format, could be either `base16` standard or [`bech32`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) standard. If the array contains multiple values, the address format toggle will appear in the UI, allowing the user to switch between formats. The first item in the array will be the default format. | - | `'["base16"]'` | `'["bech32", "base16"]'` | v1.36.0+ |
| NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX | `string` | Human-readable prefix of `bech32` address format. | Required, if `NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT` contains "bech32" value | - | `duck` | v1.36.0+ |
| NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array<AddressViewId>` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` | v1.15.0+ |
| NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS | `string` | The address of a native ERC-20 token that shadows the balance of the native coin; used to exclude its balance from the net worth value of user tokens. | - | - | `0x471EcE3750Da237f93B8E339c536989b8978a438` | v2.4.0+ |
| NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` | v1.19.0+ |
| NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS | `Array<'solidity-hardhat' \| 'solidity-foundry'>` | Pass an array of additional methods from which users can choose while verifying a smart contract. Both methods are available by default, pass `'none'` string to disable them all. | - | - | `['solidity-hardhat']` | v1.33.0+ |
| NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS | `Array<'solidity' \| 'vyper' \| 'yul' \| 'scilla' \| 'geas' \| 'stylus_rust'>` | Pass an array of contract languages that will be displayed as options in the filter on the verified contract page. | - | `['solidity','vyper','yul','geas','stylus_rust']` | `['solidity','vyper','yul','scilla']` | v1.37.0+ |
@@ -354,6 +355,7 @@ Settings for meta tags, OG tags and SEO
| NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS | `boolean` | Set to `true` to enable Submit Audit form on the contract page | - | `false` | `true` | v1.25.0+ |
| NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS | `boolean` | Set to `true` to hide indexing alert in the page header about indexing chain's blocks | - | `false` | `true` | v1.17.0+ |
| NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS | `boolean` | Set to `true` to hide indexing alert in the page footer about indexing block's internal transactions | - | `false` | `true` | v1.17.0+ |
| NEXT_PUBLIC_HIDE_NATIVE_COIN_PRICE | `boolean` | Set to `true` to hide the native coin price in the top bar | - | `false` | `true` | v2.4.0+ |
| NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE | `string` | Used for displaying custom announcements or alerts in the header of the site. Could be a regular string or a HTML code. | - | - | `Hello world! 🤪` | v1.13.0+ |
| NEXT_PUBLIC_COLOR_THEME_DEFAULT | `'light' \| 'dim' \| 'midnight' \| 'dark'` | Preferred color theme of the app | - | - | `midnight` | v1.30.0+ |
| NEXT_PUBLIC_COLOR_THEME_OVERRIDES | `string` | Color overrides for the default theme; pass a JSON-like string that represents a subset of the `DEFAULT_THEME_COLORS` object (see `toolkit/theme/foundations/colors.ts`) to customize the app's main colors. See [here](https://www.figma.com/design/4In0X8UADoZaTfZ34HaZ3K/Blockscout-design-system?node-id=29124-23813&t=XOv4ahHUSsTDlNkN-4) the Figma worksheet with description of available color tokens. | - | - | `{'text':{'primary':{'_light':{'value':'rgba(16,17,18,0.80)'},'_dark':{'value':'rgba(222,217,217)'}}}}` | v2.3.0+ |
@@ -816,7 +818,6 @@ For blockchains that use the Celo platform.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_CELO_ENABLED | `boolean` | Indicates that it is a Celo-based chain. | - | - | `true` | v1.37.0+ |
| NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS | `string` | The address of the CELO ERC-20 token. Used to exclude its balance from the net worth value of user tokens. | - | - | `0x471EcE3750Da237f93B8E339c536989b8978a438` | v2.3.0+ |
&nbsp;
+2 -2
View File
@@ -9,8 +9,8 @@ module.exports = {
if (pathname === '/tx/[hash]') {
return `/tx/${ query.hash || 'test-hash' }`;
}
if (pathname === '/clusters/[name]') {
return `/clusters/${ query.name || 'test-cluster' }`;
if (pathname === '/name-services/clusters/[name]') {
return `/name-services/clusters/${ query.name || 'test-cluster' }`;
}
return pathname || '/';
+18 -5
View File
@@ -2,6 +2,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { omit, pickBy } from 'es-toolkit';
import React from 'react';
import type { ApiName } from './types';
import type { CsrfData } from 'types/client/account';
import type { ChainConfig } from 'types/multichain';
@@ -17,6 +18,22 @@ import buildUrl from './buildUrl';
import getResourceParams from './getResourceParams';
import type { ResourceName, ResourcePathParams } from './resources';
function needCredentials(apiName: ApiName) {
if (![ 'general' ].includes(apiName)) {
return false;
}
// currently, the cookies are used only for the following features
if (
config.features.account.isEnabled ||
config.UI.views.token.hideScamTokensEnabled
) {
return true;
}
return false;
}
export interface Params<R extends ResourceName> {
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | boolean | undefined | null>;
@@ -50,11 +67,7 @@ export default function useApiFetch() {
return fetch<SuccessType, ErrorType>(
url,
{
// as of today, we use cookies only
// for user authentication in My account
// for API rate-limits (cannot use in the condition though, but we agreed with devops team that should not be an issue)
// change condition here if something is changed
credentials: config.features.account.isEnabled ? 'include' : 'same-origin',
credentials: needCredentials(apiName) ? 'include' : 'same-origin',
headers,
...(fetchParams ? omit(fetchParams, [ 'headers' ]) : {}),
},
+3 -1
View File
@@ -7,8 +7,10 @@ jest.mock('lib/api/useApiQuery', () => ({
jest.mock('configs/app', () => ({
features: {
clusters: {
nameServices: {
isEnabled: true,
ens: { isEnabled: true },
clusters: { isEnabled: true },
},
},
}));
+3 -1
View File
@@ -1,6 +1,8 @@
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
const feature = config.features.nameServices;
export function useAddressClusters(addressHash: string, isEnabled: boolean = true) {
return useApiQuery('clusters:get_clusters_by_address', {
queryParams: {
@@ -9,7 +11,7 @@ export function useAddressClusters(addressHash: string, isEnabled: boolean = tru
}),
},
queryOptions: {
enabled: Boolean(addressHash) && config.features.clusters.isEnabled && isEnabled,
enabled: Boolean(addressHash) && feature.isEnabled && feature.clusters.isEnabled && isEnabled,
},
});
}
+3 -3
View File
@@ -82,11 +82,11 @@ export default function useNavItems(): ReturnType {
icon: 'verified',
isActive: pathname === '/verified-contracts',
};
const nameLookup = config.features.nameService.isEnabled || config.features.clusters.isEnabled ? {
const nameLookup = config.features.nameServices.isEnabled ? {
text: 'Name services lookup',
nextRoute: { pathname: '/name-domains' as const },
nextRoute: { pathname: '/name-services' as const },
icon: 'name_services',
isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]' || pathname === '/clusters/[name]',
isActive: pathname.startsWith('/name-services'),
} : null;
const validators = config.features.validators.isEnabled ? {
text: 'Validators',
+1 -1
View File
@@ -7,7 +7,7 @@ const CANONICAL_ROUTES: Array<Route['pathname']> = [
'/txs',
'/ops',
'/verified-contracts',
'/name-domains',
'/name-services',
'/withdrawals',
'/tokens',
'/stats',
+3 -3
View File
@@ -24,7 +24,6 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/apps': 'Root page',
'/apps/[id]': 'Regular page',
'/essential-dapps/[id]': 'Regular page',
'/clusters/[name]': 'Regular page',
'/stats': 'Root page',
'/stats/[id]': 'Regular page',
'/uptime': 'Root page',
@@ -52,8 +51,9 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/ops': 'Root page',
'/op/[hash]': 'Regular page',
'/404': 'Regular page',
'/name-domains': 'Root page',
'/name-domains/[name]': 'Regular page',
'/name-services': 'Root page',
'/name-services/domains/[name]': 'Regular page',
'/name-services/clusters/[name]': 'Regular page',
'/validators': 'Root page',
'/validators/[id]': 'Regular page',
'/epochs': 'Root page',
+3 -3
View File
@@ -27,7 +27,6 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/apps': DEFAULT_TEMPLATE,
'/apps/[id]': DEFAULT_TEMPLATE,
'/essential-dapps/[id]': DEFAULT_TEMPLATE,
'/clusters/[name]': '%cluster_name% cluster | %app_name%',
'/stats': DEFAULT_TEMPLATE,
'/stats/[id]': DEFAULT_TEMPLATE,
'/uptime': DEFAULT_TEMPLATE,
@@ -55,8 +54,9 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/ops': DEFAULT_TEMPLATE,
'/op/[hash]': DEFAULT_TEMPLATE,
'/404': DEFAULT_TEMPLATE,
'/name-domains': DEFAULT_TEMPLATE,
'/name-domains/[name]': DEFAULT_TEMPLATE,
'/name-services': DEFAULT_TEMPLATE,
'/name-services/domains/[name]': DEFAULT_TEMPLATE,
'/name-services/clusters/[name]': DEFAULT_TEMPLATE,
'/validators': DEFAULT_TEMPLATE,
'/validators/[id]': DEFAULT_TEMPLATE,
'/epochs': DEFAULT_TEMPLATE,
+3 -3
View File
@@ -51,8 +51,9 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/ops': 'User operations on %network_name% - %network_name% explorer',
'/op/[hash]': '%network_name% user operation %hash%',
'/404': '%network_name% error - page not found',
'/name-domains': '%network_name% name domains - %network_name% explorer',
'/name-domains/[name]': '%network_name% %name% domain details',
'/name-services': '%network_name% name services - %network_name% explorer',
'/name-services/domains/[name]': '%network_name% %name% domain details',
'/name-services/clusters/[name]': '%network_name% %name% cluster details',
'/validators': '%network_name% validators list',
'/validators/[id]': '%network_name% validator %id% details',
'/epochs': '%network_name% epochs',
@@ -66,7 +67,6 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/interop-messages': '%network_name% interop messages',
'/operations': '%network_name% operations',
'/operation/[id]': '%network_name% operation %id%',
'/clusters/[name]': 'Clusters details for %name%',
'/cc/tx/[hash]': '%network_name% cross-chain transaction %hash% details',
// multichain routes
+3 -3
View File
@@ -22,7 +22,6 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/apps': 'DApps',
'/apps/[id]': 'DApp',
'/essential-dapps/[id]': 'Essential dapps',
'/clusters/[name]': 'Cluster details',
'/stats': 'Stats',
'/stats/[id]': 'Stats chart',
'/uptime': 'Uptime',
@@ -50,8 +49,9 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/ops': 'User operations',
'/op/[hash]': 'User operation details',
'/404': '404',
'/name-domains': 'Domains search and resolve',
'/name-domains/[name]': 'Domain details',
'/name-services': 'Domains search and resolve',
'/name-services/domains/[name]': 'Domain details',
'/name-services/clusters/[name]': 'Cluster details',
'/validators': 'Validators list',
'/validators/[id]': 'Validator details',
'/epochs': 'Epochs',
+3 -1
View File
@@ -5,10 +5,12 @@ import useApiQuery from 'lib/api/useApiQuery';
import useAccount from './useAccount';
const feature = config.features.nameServices;
export default function useAccountWithDomain(isEnabled: boolean) {
const { address, isConnecting } = useAccount();
const isQueryEnabled = config.features.nameService.isEnabled && Boolean(address) && Boolean(isEnabled);
const isQueryEnabled = feature.isEnabled && feature.ens.isEnabled && Boolean(address) && Boolean(isEnabled);
const domainQuery = useApiQuery('bens:address_domain', {
pathParams: {
+12 -10
View File
@@ -84,8 +84,18 @@ export const suave: Guard = (chainConfig: typeof config) => async() => {
}
};
export const nameService: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.nameService.isEnabled) {
export const nameServiceEns: Guard = (chainConfig: typeof config) => async() => {
const feature = chainConfig.features.nameServices;
if (!feature.isEnabled || !feature.ens.isEnabled) {
return {
notFound: true,
};
}
};
export const nameServiceClusters: Guard = (chainConfig: typeof config) => async() => {
const feature = chainConfig.features.nameServices;
if (!feature.isEnabled || !feature.clusters.isEnabled) {
return {
notFound: true,
};
@@ -181,14 +191,6 @@ export const pools: Guard = (chainConfig: typeof config) => async() => {
}
};
export const clusters: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.clusters.isEnabled) {
return {
notFound: true,
};
}
};
export const zetaChainCCTX: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.zetachain.isEnabled) {
return {
+3 -3
View File
@@ -12,9 +12,9 @@ export const apiDocs = factory([ guards.apiDocs ]);
export const csvExport = factory([ guards.csvExport ]);
export const stats = factory([ guards.stats ]);
export const suave = factory([ guards.suave ]);
export const nameDomain = factory([ guards.nameService, guards.clusters ]);
export const clusters = factory([ guards.clusters ]);
export const nameDomains = factory([ [ guards.nameService, guards.clusters ] ]);
export const nameServiceEns = factory([ guards.nameServiceEns ]);
export const nameServiceClusters = factory([ guards.nameServiceClusters ]);
export const nameServices = factory([ [ guards.nameServiceEns, guards.nameServiceClusters ] ]);
export const accounts = factory([ guards.accounts ]);
export const accountsLabelSearch = factory([ guards.accountsLabelSearch ]);
export const validators = factory([ guards.validators ]);
+3 -3
View File
@@ -50,7 +50,6 @@ declare module "nextjs-routes" {
| DynamicRoute<"/chain/[chain-slug]/token/[hash]/instance/[id]", { "chain-slug": string; "hash": string; "id": string }>
| DynamicRoute<"/chain/[chain-slug]/tx/[hash]", { "chain-slug": string; "hash": string }>
| StaticRoute<"/chakra">
| DynamicRoute<"/clusters/[name]", { "name": string }>
| StaticRoute<"/contract-verification">
| StaticRoute<"/csv-export">
| StaticRoute<"/deposits">
@@ -64,8 +63,9 @@ declare module "nextjs-routes" {
| StaticRoute<"/interop-messages">
| StaticRoute<"/login">
| StaticRoute<"/mud-worlds">
| DynamicRoute<"/name-domains/[name]", { "name": string }>
| StaticRoute<"/name-domains">
| DynamicRoute<"/name-services/clusters/[name]", { "name": string }>
| DynamicRoute<"/name-services/domains/[name]", { "name": string }>
| StaticRoute<"/name-services">
| DynamicRoute<"/op/[hash]", { "hash": string }>
| DynamicRoute<"/operation/[id]", { "id": string }>
| StaticRoute<"/operations">
+11 -1
View File
@@ -352,7 +352,17 @@ const DEPRECATED_ROUTES = [
{
source: '/graphiql',
destination: '/api-docs?tab=graphql_api',
permanent: false,
permanent: true,
},
{
source: '/name-domains',
destination: '/name-services',
permanent: true,
},
{
source: '/name-domains/:name',
destination: '/name-services/domains/:name',
permanent: true,
},
];
+2 -2
View File
@@ -54,8 +54,8 @@
"@emotion/react": "11.14.0",
"@growthbook/growthbook-react": "0.21.0",
"@helia/verified-fetch": "2.6.12",
"@lifi/widget": "3.25.0",
"@lifi/wallet-management": "3.6.1",
"@lifi/widget": "3.25.0",
"@metamask/post-message-stream": "^7.0.0",
"@metamask/providers": "^10.2.1",
"@monaco-editor/react": "^4.7.0",
@@ -80,7 +80,7 @@
"@scure/base": "1.1.9",
"@slise/embed-react": "^2.2.0",
"@solana/wallet-adapter-react": "0.15.39",
"@specify-sh/sdk": "0.4.1",
"@specify-sh/sdk": "0.4.2",
"@tanstack/react-query": "5.55.4",
"@tanstack/react-query-devtools": "5.55.4",
"@types/papaparse": "^5.3.5",
@@ -9,7 +9,7 @@ const Cluster = dynamic(() => import('ui/pages/Cluster'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/clusters/[name]" query={ props.query }>
<PageNextJs pathname="/name-services/clusters/[name]" query={ props.query }>
<Cluster/>
</PageNextJs>
);
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { clusters as getServerSideProps } from 'nextjs/getServerSideProps/main';
export { nameServiceClusters as getServerSideProps } from 'nextjs/getServerSideProps/main';
@@ -9,7 +9,7 @@ const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/name-domains/[name]" query={ props.query }>
<PageNextJs pathname="/name-services/domains/[name]" query={ props.query }>
<NameDomain/>
</PageNextJs>
);
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { nameDomain as getServerSideProps } from 'nextjs/getServerSideProps/main';
export { nameServiceEns as getServerSideProps } from 'nextjs/getServerSideProps/main';
@@ -4,16 +4,16 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const NameDomains = dynamic(() => import('ui/pages/NameDomains'), { ssr: false });
const NameServices = dynamic(() => import('ui/pages/NameServices'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/name-domains">
<NameDomains/>
<PageNextJs pathname="/name-services">
<NameServices/>
</PageNextJs>
);
};
export default Page;
export { nameDomains as getServerSideProps } from 'nextjs/getServerSideProps/main';
export { nameServices as getServerSideProps } from 'nextjs/getServerSideProps/main';
-1
View File
@@ -112,7 +112,6 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
],
celo: [
[ 'NEXT_PUBLIC_CELO_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS', '0x471EcE3750Da237f93B8E339c536989b8978a438' ],
],
opSuperchain: [
[ 'NEXT_PUBLIC_OP_SUPERCHAIN_ENABLED', 'true' ],
+9 -10
View File
@@ -99,16 +99,15 @@ const ChartMenu = ({
}, [ pngBackgroundColor, title, chartRef ]);
const handleSVGSavingClick = React.useCallback(() => {
charts.forEach((chart) => {
const headerRows = [
'Date', 'Value',
];
const dataRows = chart.items.map((item) => [
dayjs(item.date).format('YYYY-MM-DD'), String(item.value),
]);
saveAsCsv(headerRows, dataRows, `${ chart.name } (Blockscout stats)`);
});
}, [ charts ]);
const headerRows = [
'Date', ...charts.map((chart) => chart.name),
];
const dataRows = charts[0].items.map((item, index) => [
item.dateLabel ?? dayjs(item.date).format('YYYY-MM-DD'),
...charts.map((chart) => String(chart.items[index].value)),
]);
saveAsCsv(headerRows, dataRows, `${ title } (Blockscout stats)`);
}, [ charts, title ]);
// TS thinks window.navigator.share can't be undefined, but it can
// eslint-disable-next-line @typescript-eslint/no-explicit-any
+1 -1
View File
@@ -51,7 +51,7 @@ export type TokenTotal = Erc20TotalPayload | Erc721TotalPayload | Erc1155TotalPa
interface TokenTransferBase {
type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting';
transaction_hash: string;
transaction_hash: string | null;
from: AddressParam;
to: AddressParam;
timestamp: string;
+1 -2
View File
@@ -272,8 +272,7 @@ test.describe('update balances via socket', () => {
test('native token', async({ render, mockEnvs }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_CELO_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS', tokenInfo.tokenInfoERC20c.address_hash ],
[ 'NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS', tokenInfo.tokenInfoERC20c.address_hash ],
]);
const hooksConfig = {
router: {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

+1 -1
View File
@@ -99,7 +99,7 @@ const AddressClusters = ({ query, addressHash }: Props) => {
</div>
{ showMoreLink && (
<Link
href={ route({ pathname: '/name-domains', query: { q: addressHash, tab: 'directories' } }) }
href={ route({ pathname: '/name-services', query: { q: addressHash, tab: 'directories' } }) }
>
<span>More results</span>
<chakra.span color="text.secondary"> ({ totalRecords })</chakra.span>
+1 -1
View File
@@ -127,7 +127,7 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => {
) }
{ (ownedDomains.length > 9 || resolvedDomains.length > 9) && (
<Link
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: addressHash } }) }
href={ route({ pathname: '/name-services', query: { tab: 'domains', owned_by: 'true', resolved_to: 'true', address: addressHash } }) }
>
<span> More results</span>
<chakra.span color="text.secondary"> ({ totalRecords })</chakra.span>
+1 -2
View File
@@ -129,8 +129,7 @@ test('long values', async({ render, page, mockApiResponse }) => {
test('native token', async({ render, mockEnvs, page }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_CELO_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS', tokenInfoERC20c.address_hash ],
[ 'NEXT_PUBLIC_VIEWS_ADDRESS_NATIVE_TOKEN_ADDRESS', tokenInfoERC20c.address_hash ],
]);
await render(
+2 -3
View File
@@ -13,15 +13,14 @@ import TruncatedValue from 'ui/shared/TruncatedValue';
import type { TokenEnhancedData } from '../utils/tokenUtils';
const celoFeature = config.features.celo;
interface Props {
data: TokenEnhancedData;
}
const TokenSelectItem = ({ data }: Props) => {
const isNativeToken = celoFeature.isEnabled && data.token.address_hash.toLowerCase() === celoFeature.nativeTokenAddress?.toLowerCase();
const isNativeToken = config.UI.views.address.nativeTokenAddress &&
data.token.address_hash.toLowerCase() === config.UI.views.address.nativeTokenAddress.toLowerCase();
const secondRow = (() => {
switch (data.token.type) {
+2 -3
View File
@@ -12,8 +12,6 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
const celoFeature = config.features.celo;
type Props = AddressTokenBalance & { isLoading: boolean };
const ERC20TokensListItem = ({ token, value, isLoading }: Props) => {
@@ -23,7 +21,8 @@ const ERC20TokensListItem = ({ token, value, isLoading }: Props) => {
usd: tokenValue,
} = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 });
const isNativeToken = celoFeature.isEnabled && token.address_hash.toLowerCase() === celoFeature.nativeTokenAddress?.toLowerCase();
const isNativeToken = config.UI.views.address.nativeTokenAddress &&
token.address_hash.toLowerCase() === config.UI.views.address.nativeTokenAddress.toLowerCase();
return (
<ListItemMobile rowGap={ 2 }>
+2 -3
View File
@@ -14,8 +14,6 @@ import TokenEntity from 'ui/shared/entities/token/TokenEntity';
type Props = AddressTokenBalance & { isLoading: boolean };
const celoFeature = config.features.celo;
const ERC20TokensTableItem = ({
token,
value,
@@ -27,7 +25,8 @@ const ERC20TokensTableItem = ({
usd: tokenValue,
} = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 });
const isNativeToken = celoFeature.isEnabled && token.address_hash.toLowerCase() === celoFeature.nativeTokenAddress?.toLowerCase();
const isNativeToken = config.UI.views.address.nativeTokenAddress &&
token.address_hash.toLowerCase() === config.UI.views.address.nativeTokenAddress.toLowerCase();
return (
<TableRow role="group" >
+2 -3
View File
@@ -7,10 +7,9 @@ import config from 'configs/app';
import sumBnReducer from 'lib/bigint/sumBnReducer';
import { ZERO } from 'toolkit/utils/consts';
const celoFeature = config.features.celo;
const isNativeToken = (token: TokenEnhancedData) =>
celoFeature.isEnabled && token.token.address_hash.toLowerCase() === celoFeature.nativeTokenAddress?.toLowerCase();
config.UI.views.address.nativeTokenAddress &&
token.token.address_hash.toLowerCase() === config.UI.views.address.nativeTokenAddress.toLowerCase();
export type TokenEnhancedData = AddressTokenBalance & {
usd?: BigNumber ;
+3 -1
View File
@@ -6,10 +6,12 @@ import useApiQuery from 'lib/api/useApiQuery';
const DOMAIN_NAME_REGEXP = /.\../;
const feature = config.features.nameServices;
export default function useCheckDomainNameParam(hashOrDomainName: string) {
const router = useRouter();
const maybeDomainName = DOMAIN_NAME_REGEXP.test(hashOrDomainName);
const isQueryEnabled = config.features.nameService.isEnabled && maybeDomainName;
const isQueryEnabled = feature.isEnabled && feature.ens.isEnabled && maybeDomainName;
const [ isLoading, setIsLoading ] = React.useState(isQueryEnabled);
const domainLookupQuery = useApiQuery('bens:domains_lookup', {
@@ -3,7 +3,7 @@ import { Box, Flex } from '@chakra-ui/react';
import type { AllowanceType } from 'types/client/revoke';
import type { ChainConfig } from 'types/multichain';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
import DataListDisplay from 'ui/shared/DataListDisplay';
import ApprovalsListItem from './ApprovalsListItem';
import ApprovalsTable from './ApprovalsTable';
@@ -23,25 +23,20 @@ export default function Approvals({
isAddressMatch,
hideApproval,
}: Props) {
return (
const content = (
<>
<Box hideFrom="lg">
<Flex flexDirection="column">
{ approvals.map((approval, index) => (
<ApprovalsListItem
key={ index }
selectedChain={ selectedChain }
approval={ approval }
isLoading={ isLoading }
isAddressMatch={ isAddressMatch }
hideApproval={ hideApproval }
/>
)) }
{ !isLoading && !approvals.length && (
<EmptySearchResult text="No approvals found"/>
) }
</Flex>
</Box>
<Flex hideFrom="lg" flexDirection="column">
{ approvals.map((approval, index) => (
<ApprovalsListItem
key={ index }
selectedChain={ selectedChain }
approval={ approval }
isLoading={ isLoading }
isAddressMatch={ isAddressMatch }
hideApproval={ hideApproval }
/>
)) }
</Flex>
<Box hideBelow="lg">
<ApprovalsTable
selectedChain={ selectedChain }
@@ -53,4 +48,17 @@ export default function Approvals({
</Box>
</>
);
return (
<DataListDisplay
itemsNum={ approvals.length }
isError={ false }
filterProps={{
emptyFilteredText: 'No approvals found',
hasActiveFilters: true,
}}
>
{ content }
</DataListDisplay>
);
}
@@ -72,14 +72,16 @@ export default function ApprovalsListItem({
}}
isLoading={ isLoading }
noCopy
noLink
jointSymbol
href={ selectedChain?.config.app.baseUrl + route({ pathname: '/token/[hash]', query: { hash: approval.address } }) }
link={{ noIcon: true, external: true }}
/>
<AddressEntity
address={{ hash: approval.address }}
truncation="constant"
noIcon
isLoading={ isLoading }
href={ selectedChain?.config.app.baseUrl + route({ pathname: '/token/[hash]', query: { hash: approval.address } }) }
href={ selectedChain?.config.app.baseUrl + route({ pathname: '/address/[hash]', query: { hash: approval.address } }) }
link={{ noIcon: true, external: true }}
/>
</ListItemMobileGrid.Value>
@@ -4,7 +4,6 @@ import type { AllowanceType } from 'types/client/revoke';
import type { ChainConfig } from 'types/multichain';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
import TimeFormatToggle from 'ui/shared/time/TimeFormatToggle';
import ApprovalsTableItem from './ApprovalsTableItem';
@@ -25,42 +24,37 @@ export default function ApprovalsTable({
hideApproval,
}: Props) {
return (
<>
<TableRoot>
<TableHeaderSticky top={ 136 }>
<TableRow>
<TableColumnHeader w="30%">Token</TableColumnHeader>
<TableColumnHeader w="15%">Approved spender</TableColumnHeader>
<TableColumnHeader w="20%" isNumeric>
Approved amount
</TableColumnHeader>
<TableColumnHeader w="17%" isNumeric>
Value at risk
</TableColumnHeader>
<TableColumnHeader w={ isAddressMatch ? '30px' : '50px' }/>
<TableColumnHeader w="18%">
Last updated
<TimeFormatToggle/>
</TableColumnHeader>
{ isAddressMatch && <TableColumnHeader w="95px" isNumeric/> }
</TableRow>
</TableHeaderSticky>
<TableBody>
{ approvals.map((approval, index) => (
<ApprovalsTableItem
key={ index }
selectedChain={ selectedChain }
approval={ approval }
isLoading={ isLoading }
isAddressMatch={ isAddressMatch }
hideApproval={ hideApproval }
/>
)) }
</TableBody>
</TableRoot>
{ !isLoading && !approvals.length && (
<EmptySearchResult text="No approvals found"/>
) }
</>
<TableRoot>
<TableHeaderSticky top={ 136 }>
<TableRow>
<TableColumnHeader w="30%">Token</TableColumnHeader>
<TableColumnHeader w="15%">Approved spender</TableColumnHeader>
<TableColumnHeader w="20%" isNumeric>
Approved amount
</TableColumnHeader>
<TableColumnHeader w="17%" isNumeric>
Value at risk
</TableColumnHeader>
<TableColumnHeader w={ isAddressMatch ? '30px' : '50px' }/>
<TableColumnHeader w="18%">
Last updated
<TimeFormatToggle/>
</TableColumnHeader>
{ isAddressMatch && <TableColumnHeader w="95px" isNumeric/> }
</TableRow>
</TableHeaderSticky>
<TableBody>
{ approvals.map((approval, index) => (
<ApprovalsTableItem
key={ index }
selectedChain={ selectedChain }
approval={ approval }
isLoading={ isLoading }
isAddressMatch={ isAddressMatch }
hideApproval={ hideApproval }
/>
)) }
</TableBody>
</TableRoot>
);
}
@@ -72,7 +72,7 @@ export default function ApprovalsTableItem({
truncation="constant"
noIcon
isLoading={ isLoading }
href={ selectedChain?.config.app.baseUrl + route({ pathname: '/token/[hash]', query: { hash: approval.address } }) }
href={ selectedChain?.config.app.baseUrl + route({ pathname: '/address/[hash]', query: { hash: approval.address } }) }
link={{ variant: 'secondary', noIcon: true, external: true }}
/>
</Flex>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 86 KiB

+12 -3
View File
@@ -121,7 +121,10 @@ const NameDomainDetails = ({ query }: Props) => {
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.registrant.hash } }) }
href={ route({
pathname: '/name-services',
query: { tab: 'domains', owned_by: 'true', resolved_to: 'true', address: query.data.registrant.hash },
}) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</Link>
@@ -150,7 +153,10 @@ const NameDomainDetails = ({ query }: Props) => {
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.owner.hash } }) }
href={ route({
pathname: '/name-services',
query: { tab: 'domains', owned_by: 'true', resolved_to: 'true', address: query.data.owner.hash },
}) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</Link>
@@ -179,7 +185,10 @@ const NameDomainDetails = ({ query }: Props) => {
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.wrapped_owner.hash } }) }
href={ route({
pathname: '/name-services',
query: { tab: 'domains', owned_by: 'true', resolved_to: 'true', address: query.data.wrapped_owner.hash },
}) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</Link>
@@ -28,6 +28,8 @@ const ClustersDirectoryTableItem = ({ item, isLoading, isClusterDetailsLoading }
isLoading={ isLoading }
fontWeight={ 500 }
noLink={ !isEvmAddress(item.owner) }
w="fit-content"
maxW="100%"
/>
) }
{ !item.owner && <Skeleton loading={ isLoading }></Skeleton> }
@@ -23,7 +23,7 @@ import NameDomainsTable from './NameDomainsTable';
import type { Sort, SortField } from './utils';
import { SORT_OPTIONS, getNextSortValue } from './utils';
const NameDomainsContent = () => {
const NameDomains = () => {
const router = useRouter();
const q = getQueryParamString(router.query.name) || getQueryParamString(router.query.address);
@@ -244,4 +244,4 @@ const NameDomainsContent = () => {
);
};
export default NameDomainsContent;
export default NameDomains;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

+5 -4
View File
@@ -68,6 +68,7 @@ const PREDEFINED_TAG_PRIORITY = 100;
const txInterpretation = config.features.txInterpretation;
const addressProfileAPIFeature = config.features.addressProfileAPI;
const xScoreFeature = config.features.xStarScore;
const nameServicesFeature = config.features.nameServices;
const AddressPageContent = () => {
const router = useRouter();
@@ -126,7 +127,7 @@ const AddressPageContent = () => {
order: 'ASC',
},
queryOptions: {
enabled: Boolean(hash) && config.features.nameService.isEnabled,
enabled: Boolean(hash) && nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled,
},
});
const addressMainDomain = !addressQuery.isPlaceholderData ?
@@ -166,7 +167,7 @@ const AddressPageContent = () => {
isEnabled: !countersQuery.isPlaceholderData && !countersQuery.isDegradedData,
});
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined);
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? addressQuery.data.hash : undefined);
const xStarQuery = useFetchXStarScore({ hash });
@@ -450,9 +451,9 @@ const AddressPageContent = () => {
<HStack ml="auto" gap={ 2 }/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
<SolidityscanReport hash={ hash }/> }
{ !isLoading && config.features.nameService.isEnabled &&
{ !isLoading && nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled &&
<AddressEnsDomains query={ addressEnsDomainsQuery } addressHash={ hash } mainDomainName={ addressQuery.data?.ens_domain_name }/> }
{ !isLoading && config.features.clusters.isEnabled &&
{ !isLoading && nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled &&
<AddressClusters query={ addressClustersQuery } addressHash={ hash }/> }
<NetworkExplorers type="address" pathParam={ hash }/>
</Flex>
+4 -1
View File
@@ -69,7 +69,10 @@ const NameDomain = () => {
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: infoQuery.data?.resolved_address?.hash } }) }
href={ route({
pathname: '/name-services',
query: { tab: 'domains', owned_by: 'true', resolved_to: 'true', address: infoQuery.data?.resolved_address?.hash },
}) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</Link>
-28
View File
@@ -1,28 +0,0 @@
import React from 'react';
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
import config from 'configs/app';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import Clusters from 'ui/nameDomains/directories/Clusters';
import NameDomainsContent from 'ui/nameDomains/domains/NameDomainsContent';
import PageTitle from 'ui/shared/Page/PageTitle';
const NameDomains = () => {
const tabs: Array<TabItemRegular> = [
config.features.nameService.isEnabled && { id: 'domains', title: 'Domains', component: <NameDomainsContent/> },
config.features.clusters.isEnabled && { id: 'directories', title: 'Directories', component: <Clusters/> },
].filter(Boolean);
return (
<>
<PageTitle
title={ config.meta.seo.enhancedDataEnabled ? `${ config.chain.name } name domains` : 'Name services lookup' }
withTextAd
/>
<RoutedTabs tabs={ tabs }/>
</>
);
};
export default NameDomains;
@@ -7,7 +7,7 @@ import * as ensDomainMock from 'mocks/ens/domain';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect, devices } from 'playwright/lib';
import NameDomains from './NameDomains';
import NameServices from './NameServices';
test.describe('domains', () => {
@@ -49,7 +49,7 @@ test.describe('domains', () => {
test('default view', async({ render }) => {
test.slow();
const component = await render(<NameDomains/>, { hooksConfig });
const component = await render(<NameServices/>, { hooksConfig });
await expect(component).toHaveScreenshot({ timeout: 10_000 });
});
@@ -57,13 +57,13 @@ test.describe('domains', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('default view', async({ render }) => {
const component = await render(<NameDomains/>, { hooksConfig });
const component = await render(<NameServices/>, { hooksConfig });
await expect(component).toHaveScreenshot({ timeout: 10_000 });
});
});
test('filters', async({ render, page }) => {
const component = await render(<NameDomains/>, { hooksConfig });
const component = await render(<NameServices/>, { hooksConfig });
await component.getByRole('button', { name: 'Filter' }).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 500 } });
@@ -120,12 +120,12 @@ test.describe('directories', () => {
});
test('directory view', async({ render }) => {
const component = await render(<NameDomains/>, { hooksConfig: hooksConfig.directories });
const component = await render(<NameServices/>, { hooksConfig: hooksConfig.directories });
await expect(component).toHaveScreenshot();
});
test('leaderboard view', async({ render }) => {
const component = await render(<NameDomains/>, { hooksConfig: hooksConfig.leaderboard });
const component = await render(<NameServices/>, { hooksConfig: hooksConfig.leaderboard });
await expect(component).toHaveScreenshot();
});
@@ -133,12 +133,12 @@ test.describe('directories', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('directory view', async({ render }) => {
const component = await render(<NameDomains/>, { hooksConfig: hooksConfig.directories });
const component = await render(<NameServices/>, { hooksConfig: hooksConfig.directories });
await expect(component).toHaveScreenshot();
});
test('leaderboard view', async({ render }) => {
const component = await render(<NameDomains/>, { hooksConfig: hooksConfig.leaderboard });
const component = await render(<NameServices/>, { hooksConfig: hooksConfig.leaderboard });
await expect(component).toHaveScreenshot();
});
});
+30
View File
@@ -0,0 +1,30 @@
import React from 'react';
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
import config from 'configs/app';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import Clusters from 'ui/nameServices/directories/Clusters';
import NameDomains from 'ui/nameServices/domains/NameDomains';
import PageTitle from 'ui/shared/Page/PageTitle';
const feature = config.features.nameServices;
const NameServices = () => {
const tabs: Array<TabItemRegular> = [
feature.isEnabled && feature.ens.isEnabled && { id: 'domains', title: 'Domains', component: <NameDomains/> },
feature.isEnabled && feature.clusters.isEnabled && { id: 'directories', title: 'Directories', component: <Clusters/> },
].filter(Boolean);
return (
<>
<PageTitle
title={ config.meta.seo.enhancedDataEnabled ? `${ config.chain.name } name services` : 'Name services lookup' }
withTextAd
/>
<RoutedTabs tabs={ tabs }/>
</>
);
};
export default NameServices;
+3 -1
View File
@@ -32,6 +32,8 @@ import HeaderMobile from 'ui/snippets/header/HeaderMobile';
import SearchBarSuggestBlockCountdown from 'ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestBlockCountdown';
import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery';
const nameServicesFeature = config.features.nameServices;
const SearchResultsPageContent = () => {
const router = useRouter();
const withRedirectCheck = getQueryParamString(router.query.redirect) === 'true';
@@ -114,7 +116,7 @@ const SearchResultsPageContent = () => {
if (!config.features.dataAvailability.isEnabled && item.type === 'blob') {
return false;
}
if (!config.features.nameService.isEnabled && item.type === 'ens_domain') {
if ((!nameServicesFeature.isEnabled || !nameServicesFeature.ens.isEnabled) && item.type === 'ens_domain') {
return false;
}
if (!config.features.tac.isEnabled && item.type === 'tac_operation') {

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

+14 -9
View File
@@ -56,18 +56,23 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => {
}, [ router ]);
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
const token = await recaptcha.executeAsync();
if (!token) {
throw new Error('ReCaptcha is not solved');
}
const requestsBody = convertFormDataToRequestsBody(data);
const result = await Promise.all(requestsBody.map(async(body) => {
return recaptcha.executeAsync()
.then(() => {
return apiFetch<'admin:public_tag_application', unknown, { message: string }>('admin:public_tag_application', {
pathParams: { chainId: appConfig.chain.id },
fetchParams: {
method: 'POST',
body: { submission: body },
},
});
return apiFetch<'admin:public_tag_application', unknown, { message: string }>(
'admin:public_tag_application', {
pathParams: { chainId: appConfig.chain.id },
fetchParams: {
method: 'POST',
body: { submission: body },
},
})
.then(() => ({ error: null, payload: body }))
.catch((error: unknown) => {
+3 -3
View File
@@ -1,8 +1,6 @@
import { Box } from '@chakra-ui/react';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import config from 'configs/app';
import { Image } from 'toolkit/chakra/image';
import type { ImageProps } from 'toolkit/chakra/image';
@@ -12,6 +10,8 @@ interface ClusterIconProps extends Omit<ImageProps, 'src' | 'alt'> {
clusterName: string;
}
const nameServicesFeature = config.features.nameServices;
const ClusterIcon = ({
clusterName,
boxSize = 5,
@@ -20,7 +20,7 @@ const ClusterIcon = ({
flexShrink = 0,
...imageProps
}: ClusterIconProps) => {
const clustersFeature = getFeaturePayload(config.features.clusters);
const clustersFeature = nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled ? nameServicesFeature.clusters : undefined;
const fallbackElement = (
<Box
@@ -72,15 +72,17 @@ const TokenTransferListItem = ({
{ total && 'token_id' in total && total.token_id !== null && token && (
<NftEntity hash={ token.address_hash } id={ total.token_id } instance={ total.token_instance } isLoading={ isLoading }/>
) }
{ showTxInfo && txHash && (
{ showTxInfo && (
<Flex justifyContent="space-between" alignItems="center" lineHeight="24px" width="100%">
<TxEntity
isLoading={ isLoading }
hash={ txHash }
truncation="constant_long"
fontWeight="700"
chain={ chainData }
/>
{ txHash && (
<TxEntity
isLoading={ isLoading }
hash={ txHash }
truncation="constant_long"
fontWeight="700"
chain={ chainData }
/>
) }
<TimeWithTooltip
timestamp={ timestamp }
enableIncrement={ enableTimeIncrement }
@@ -50,11 +50,17 @@ const TokenTransferTableItem = ({
return (
<TableRow alignItems="top">
{ showTxInfo && txHash && (
{ showTxInfo && (
<TableCell>
<Box my="3px" textAlign="center">
<TxAdditionalInfo hash={ txHash } isLoading={ isLoading }/>
</Box>
{
txHash ? (
<Box my="3px" textAlign="center">
<TxAdditionalInfo hash={ txHash } isLoading={ isLoading }/>
</Box>
) : (
<div/>
)
}
</TableCell>
) }
{ chainData && (
@@ -89,16 +95,20 @@ const TokenTransferTableItem = ({
/>
) }
</TableCell>
{ showTxInfo && txHash && (
{ showTxInfo && (
<TableCell>
<TxEntity
hash={ txHash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
mt="7px"
truncation="constant_long"
/>
{ txHash ? (
<TxEntity
hash={ txHash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
mt={ 1 }
truncation="constant_long"
/>
) : (
<Skeleton loading={ isLoading } mt={ 1 }>-</Skeleton>
) }
<TimeWithTooltip
timestamp={ timestamp }
enableIncrement={ enableTimeIncrement }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 119 KiB

+4 -18
View File
@@ -1,35 +1,21 @@
import React from 'react';
import config from 'configs/app';
import { Link } from 'toolkit/chakra/link';
import { Tag, type TagProps } from 'toolkit/chakra/tag';
import { Tooltip } from 'toolkit/chakra/tooltip';
const feature = config.features.celo;
interface Props extends TagProps {}
const NativeTokenTag = (props: Props) => {
const handleLinkClick = React.useCallback((event: React.MouseEvent<HTMLAnchorElement>) => {
event.stopPropagation();
}, []);
if (!feature.isEnabled || !feature.nativeTokenAddress) {
if (!config.UI.views.address.nativeTokenAddress) {
return null;
}
const tooltipContent = (
<>
<span>This ERC-20 token represents the native CELO balance for this address and isnt counted twice. </span>
<Link href="https://docs.celo.org/what-is-celo/using-celo/protocol/celo-token" external onClick={ handleLinkClick }>
Learn more
</Link>
</>
);
return (
<Tooltip content={ tooltipContent } interactive>
<Tooltip
content={ `This ERC-20 token represents the native ${ config.chain.currency.symbol } balance for this address and isnt counted twice` }
>
<Tag { ...props }>Native token</Tag>
</Tooltip>
);
@@ -24,7 +24,7 @@ describe('ClustersEntity', () => {
render(<ClustersEntity clusterName={ mockClusterName }/>);
const link = screen.getByRole('link');
expect(link.getAttribute('href')).toBe('/clusters/test-cluster');
expect(link.getAttribute('href')).toBe('/name-services/clusters/test-cluster');
});
it('should render without link when noLink is true', () => {
@@ -1,8 +1,6 @@
import { Box, chakra, Flex, Text } from '@chakra-ui/react';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import { route } from 'nextjs-routes';
import config from 'configs/app';
@@ -15,10 +13,13 @@ import IconSvg from 'ui/shared/IconSvg';
import { distributeEntityProps, getIconProps } from '../base/utils';
const nameServicesFeature = config.features.nameServices;
const clustersFeature = nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled ? nameServicesFeature.clusters : undefined;
type LinkProps = EntityBase.LinkBaseProps & Pick<EntityProps, 'clusterName'>;
const Link = chakra((props: LinkProps) => {
const defaultHref = route({ pathname: '/clusters/[name]', query: { name: encodeURIComponent(props.clusterName) } });
const defaultHref = route({ pathname: '/name-services/clusters/[name]', query: { name: encodeURIComponent(props.clusterName) } });
return (
<EntityBase.Link
@@ -72,7 +73,7 @@ const Icon = (props: IconProps) => {
borderRadius="base"
mr={ 2 }
flexShrink={ 0 }
src={ `${ getFeaturePayload(config.features.clusters)?.cdnUrl || '' }/profile-image/${ props.clusterName }` }
src={ `${ clustersFeature?.cdnUrl || '' }/profile-image/${ props.clusterName }` }
alt={ `${ props.clusterName } profile` }
fallback={ fallbackElement }
/>
+1 -1
View File
@@ -17,7 +17,7 @@ import { distributeEntityProps, getIconProps } from '../base/utils';
type LinkProps = EntityBase.LinkBaseProps & Pick<EntityProps, 'domain'>;
const Link = chakra((props: LinkProps) => {
const defaultHref = route({ pathname: '/name-domains/[name]', query: { name: props.domain } });
const defaultHref = route({ pathname: '/name-services/domains/[name]', query: { name: props.domain } });
return (
<EntityBase.Link
+4 -2
View File
@@ -4,6 +4,8 @@ import type { SearchResultItem } from 'types/client/search';
import config from 'configs/app';
const nameServicesFeature = config.features.nameServices;
export type ApiCategory =
'token' |
'nft' |
@@ -48,11 +50,11 @@ if (config.features.dataAvailability.isEnabled) {
searchCategories.push({ id: 'blob', title: 'Blobs', tabTitle: 'Blobs' });
}
if (config.features.nameService.isEnabled) {
if (nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled) {
searchCategories.unshift({ id: 'domain', title: 'Names', tabTitle: 'Names' });
}
if (config.features.clusters.isEnabled) {
if (nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled) {
searchCategories.unshift({ id: 'cluster', title: 'Cluster Name', tabTitle: 'Cluster' });
}
@@ -40,6 +40,8 @@ import {
WEI_VAR_NAME,
} from './utils';
const nameServicesFeature = config.features.nameServices;
interface Props extends BoxProps {
summary?: TxInterpretationSummary;
isLoading?: boolean;
@@ -102,7 +104,7 @@ const TxInterpretationElementByType = (
</chakra.span>
);
case 'domain': {
if (config.features.nameService.isEnabled) {
if (nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled) {
return (
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<EnsEntity
+4 -1
View File
@@ -10,6 +10,9 @@ import { Input } from 'toolkit/chakra/input';
import { InputGroup } from 'toolkit/chakra/input-group';
import { ClearButton } from 'toolkit/components/buttons/ClearButton';
import IconSvg from 'ui/shared/IconSvg';
const nameServicesFeature = config.features.nameServices;
interface Props extends Omit<HTMLChakraProps<'form'>, 'onChange'> {
onChange?: (value: string) => void;
onSubmit?: (event: FormEvent<HTMLFormElement>) => void;
@@ -77,7 +80,7 @@ const SearchBarInput = (
}, [ handleKeyPress ]);
const getPlaceholder = () => {
const clusterText = config.features.clusters.isEnabled ? ' / cluster ' : '';
const clusterText = nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled ? ' / cluster ' : '';
return `Search by address / txn hash / block / token${ clusterText }/... `;
};
@@ -52,7 +52,7 @@ const defaultUseQueryResult: Partial<UseQueryResult> = {
jest.mock('configs/app', () => ({
features: {
clusters: { isEnabled: true },
nameServices: { isEnabled: true, ens: { isEnabled: true }, clusters: { isEnabled: true } },
rollbar: { isEnabled: false },
},
UI: {
@@ -10,6 +10,9 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import useQuickSearchQuery from './useQuickSearchQuery';
const nameServicesFeature = config.features.nameServices;
const isClustersEnabled = nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled;
function isClusterSearch(term: string): boolean {
const trimmed = term.trim();
const hasTrailingSlash = trimmed.endsWith('/');
@@ -52,7 +55,7 @@ function transformClusterToSearchResult(cluster: {
export default function useSearchWithClusters() {
const quickSearch = useQuickSearchQuery();
const isClusterQuery = config.features.clusters.isEnabled ?
const isClusterQuery = isClustersEnabled ?
isClusterSearch(quickSearch.debouncedSearchTerm) : false;
const clusterName = isClusterQuery ? extractClusterName(quickSearch.debouncedSearchTerm) : '';
@@ -75,7 +78,7 @@ export default function useSearchWithClusters() {
return null;
}
},
enabled: config.features.clusters.isEnabled && isClusterQuery && clusterName.length > 0,
enabled: isClustersEnabled && isClusterQuery && clusterName.length > 0,
select: (data) => {
if (!data?.result?.data) return [];
return [ transformClusterToSearchResult(data.result.data, data.result.data.owner) ];
@@ -83,7 +86,7 @@ export default function useSearchWithClusters() {
});
const combinedQuery = React.useMemo(() => {
if (!config.features.clusters.isEnabled || !isClusterQuery) {
if (!isClustersEnabled || !isClusterQuery) {
return quickSearch.query;
}
@@ -98,5 +101,5 @@ export default function useSearchWithClusters() {
combinedQuery,
]);
return config.features.clusters.isEnabled ? result : quickSearch;
return isClustersEnabled ? result : quickSearch;
}
+9 -5
View File
@@ -49,13 +49,17 @@ const TopBarStats = () => {
return <div/>;
}
const hasNativeCoinPrice = data?.coin_price && !config.UI.nativeCoinPrice.isHidden;
const hasSecondaryCoinPrice = data?.secondary_coin_price && config.chain.secondaryCoin.symbol && (hasNativeCoinPrice ? !isMobile : true);
const hasGasInfo = data?.gas_prices && data.gas_prices.average !== null && config.features.gasTracker.isEnabled;
return (
<Flex
alignItems="center"
fontSize="xs"
fontWeight={ 500 }
>
{ data?.coin_price && (
{ hasNativeCoinPrice && (
<Flex columnGap={ 1 }>
<Skeleton loading={ isPlaceholderData }>
<chakra.span color="text.secondary">{ config.chain.currency.symbol } </chakra.span>
@@ -70,7 +74,7 @@ const TopBarStats = () => {
) }
</Flex>
) }
{ !isMobile && data?.secondary_coin_price && config.chain.secondaryCoin.symbol && (
{ hasSecondaryCoinPrice && (
<Flex columnGap={ 1 } ml={ data?.coin_price ? 3 : 0 }>
<Skeleton loading={ isPlaceholderData }>
<chakra.span color="text.secondary">{ config.chain.secondaryCoin.symbol } </chakra.span>
@@ -78,14 +82,14 @@ const TopBarStats = () => {
</Skeleton>
</Flex>
) }
{ data?.coin_price && config.features.gasTracker.isEnabled && <TextSeparator/> }
{ data?.gas_prices && data.gas_prices.average !== null && config.features.gasTracker.isEnabled && (
{ (hasNativeCoinPrice || hasSecondaryCoinPrice) && hasGasInfo && <TextSeparator/> }
{ hasGasInfo && (
<>
<Skeleton loading={ isPlaceholderData } display={{ base: 'none', lg: 'inline-flex' }} whiteSpace="pre-wrap">
<chakra.span color="text.secondary">Gas </chakra.span>
<GasInfoTooltip data={ data } dataUpdatedAt={ dataUpdatedAt } placement={ !data?.coin_price ? 'bottom-start' : undefined }>
<Link>
<GasPrice data={ data.gas_prices.average }/>
<GasPrice data={ data.gas_prices?.average ?? null }/>
</Link>
</GasInfoTooltip>
</Skeleton>
@@ -40,12 +40,14 @@ const TokenTransferListItem = ({
return (
<ListItemMobile rowGap={ 3 }>
<Flex justifyContent="space-between" alignItems="center" lineHeight="24px" width="100%">
<TxEntity
isLoading={ isLoading }
hash={ txHash }
truncation="constant_long"
fontWeight="700"
/>
{ txHash && (
<TxEntity
isLoading={ isLoading }
hash={ txHash }
truncation="constant_long"
fontWeight="700"
/>
) }
<TimeWithTooltip
timestamp={ timestamp }
enableIncrement
@@ -40,13 +40,15 @@ const TokenTransferTableItem = ({
<TableRow alignItems="top">
<TableCell>
<Flex flexDirection="column" alignItems="flex-start" mt="5px" rowGap={ 3 }>
<TxEntity
hash={ txHash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
truncation="constant_long"
/>
{ txHash ? (
<TxEntity
hash={ txHash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
truncation="constant_long"
/>
) : <Skeleton loading={ isLoading }>-</Skeleton> }
<TimeWithTooltip
timestamp={ timestamp }
enableIncrement
+8 -4
View File
@@ -34,10 +34,14 @@ const TokenTransfersListItem = ({ item, isLoading, chainData }: Props) => {
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntity hash={ item.transaction_hash } isLoading={ isLoading } truncation="constant_long" noIcon={ !chainData } chain={ chainData }/>
</ListItemMobileGrid.Value>
{ item.transaction_hash && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntity hash={ item.transaction_hash } isLoading={ isLoading } truncation="constant_long" noIcon={ !chainData } chain={ chainData }/>
</ListItemMobileGrid.Value>
</>
) }
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
+11 -7
View File
@@ -40,13 +40,17 @@ const TokenTransferTableItem = ({ item, isLoading, chainData }: Props) => {
</TableCell>
) }
<TableCell>
<TxEntity
hash={ item.transaction_hash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
truncation="constant_long"
/>
{ item.transaction_hash ? (
<TxEntity
hash={ item.transaction_hash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
truncation="constant_long"
/>
) : (
<Skeleton loading={ isLoading }>-</Skeleton>
) }
<TimeWithTooltip
timestamp={ item.timestamp }
enableIncrement
@@ -206,8 +206,9 @@ const ZetaChainCCTXDetailsLifecycleOut = ({ outboundParam, tx, isLoading, isLast
/>
</>
);
text = `Abort executed`;
color = 'text.success';
const isFailed = tx.cctx_status?.is_abort_refunded === false;
text = isFailed ? `Abort failed` : `Abort executed`;
color = isFailed ? 'text.error' : 'text.success';
}
}
+4 -4
View File
@@ -7488,10 +7488,10 @@
rpc-websockets "^9.0.2"
superstruct "^2.0.2"
"@specify-sh/sdk@0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@specify-sh/sdk/-/sdk-0.4.1.tgz#016cd004c35fa64b8a510daac675edfce012958f"
integrity sha512-IY5xUFbZo3CYfBh+iRqIP3sguUyJhgY/hWA2EvBrwwOrgLL9vjJVEI+ijSYRlNBBTTEHreB0KC6eK0USO87jYQ==
"@specify-sh/sdk@0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@specify-sh/sdk/-/sdk-0.4.2.tgz#409072780661e9ba6c9d773403a1252b26ddc92b"
integrity sha512-vskPvwpE3SWkmrOPhfMgvmvxvC+XNL93FTTF4SD3cPNZDsry5qfPNIW6TU1OTh3DWk7JMl/3X605CJUUAdEYmw==
dependencies:
cross-fetch "4.0.0"