Skip to content

Commit 8c6d740

Browse files
authored
Support bech32 address standard (blockscout#2351)
* create settings context with simple address format toggler * transform base16 hash to bech32 hash * add ENV variables * add snippet to address page * add redirect for bech32 addresses * change address format in search * add provider to tests * update demo values * migrate from Buffer to Uint8Array and add tests * bug fixes and screenshots updates * review fixes * roll back changes in env values * update screenshots
1 parent e0b89d0 commit 8c6d740

File tree

66 files changed

+509
-131
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+509
-131
lines changed

configs/app/ui/views/address.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { SmartContractVerificationMethodExtra } from 'types/client/contract';
22
import { SMART_CONTRACT_EXTRA_VERIFICATION_METHODS } from 'types/client/contract';
3-
import type { AddressViewId, IdenticonType } from 'types/views/address';
4-
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from 'types/views/address';
3+
import type { AddressFormat, AddressViewId, IdenticonType } from 'types/views/address';
4+
import { ADDRESS_FORMATS, ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from 'types/views/address';
55

66
import { getEnvValue, parseEnvJson } from 'configs/app/utils';
77

@@ -11,6 +11,28 @@ const identiconType: IdenticonType = (() => {
1111
return IDENTICON_TYPES.find((type) => value === type) || 'jazzicon';
1212
})();
1313

14+
const formats: Array<AddressFormat> = (() => {
15+
const value = (parseEnvJson<Array<AddressFormat>>(getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT')) || [])
16+
.filter((format) => ADDRESS_FORMATS.includes(format));
17+
18+
if (value.length === 0) {
19+
return [ 'base16' ];
20+
}
21+
22+
return value;
23+
})();
24+
25+
const bech32Prefix = (() => {
26+
const value = getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX');
27+
28+
if (!value || !formats.includes('bech32')) {
29+
return undefined;
30+
}
31+
32+
// these are the limits of the bech32 prefix - https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
33+
return value.length >= 1 && value.length <= 83 ? value : undefined;
34+
})();
35+
1436
const hiddenViews = (() => {
1537
const parsedValue = parseEnvJson<Array<AddressViewId>>(getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS')) || [];
1638

@@ -43,6 +65,10 @@ const extraVerificationMethods: Array<SmartContractVerificationMethodExtra> = ((
4365

4466
const config = Object.freeze({
4567
identiconType,
68+
hashFormat: {
69+
availableFormats: formats,
70+
bech32Prefix,
71+
},
4672
hiddenViews,
4773
solidityscanEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED') === 'true',
4874
extraVerificationMethods,

configs/envs/.env.pw

+2
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ NEXT_PUBLIC_METADATA_SERVICE_API_HOST=http://localhost:3007
5454
NEXT_PUBLIC_NAME_SERVICE_API_HOST=http://localhost:3008
5555
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx
5656
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
57+
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32']
58+
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=tom

deploy/tools/envs-validator/schema.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import type { ChainIndicatorId, HeroBannerButtonState, HeroBannerConfig, HomeSta
3535
import { type NetworkVerificationTypeEnvs, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks';
3636
import { COLOR_THEME_IDS } from '../../../types/settings';
3737
import type { FontFamily } from '../../../types/ui';
38-
import type { AddressViewId } from '../../../types/views/address';
39-
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
38+
import type { AddressFormat, AddressViewId } from '../../../types/views/address';
39+
import { ADDRESS_FORMATS, ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
4040
import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
4141
import type { BlockFieldId } from '../../../types/views/block';
4242
import type { NftMarketplaceItem } from '../../../types/views/nft';
@@ -658,6 +658,19 @@ const schema = yup
658658
.json()
659659
.of(yup.string<BlockFieldId>().oneOf(BLOCK_FIELDS_IDS)),
660660
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: yup.string().oneOf(IDENTICON_TYPES),
661+
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT: yup
662+
.array()
663+
.transform(replaceQuotes)
664+
.json()
665+
.of(yup.string<AddressFormat>().oneOf(ADDRESS_FORMATS)),
666+
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX: yup
667+
.string()
668+
.when('NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT', {
669+
is: (value: Array<AddressFormat> | undefined) => value && value.includes('bech32'),
670+
then: (schema) => schema.required().min(1).max(83),
671+
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX is required if NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT contains "bech32"'),
672+
}),
673+
661674
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: yup
662675
.array()
663676
.transform(replaceQuotes)
+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none
22
NEXT_PUBLIC_API_SPEC_URL=none
33
NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none
4-
NEXT_PUBLIC_HOMEPAGE_STATS=[]
4+
NEXT_PUBLIC_HOMEPAGE_STATS=[]
5+
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32']
6+
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo

deploy/tools/envs-validator/test/.env.base

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ NEXT_PUBLIC_STATS_API_HOST=https://example.com
7070
NEXT_PUBLIC_STATS_API_BASE_PATH=/
7171
NEXT_PUBLIC_USE_NEXT_JS_PROXY=false
7272
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar
73+
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16']
7374
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts']
7475
NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=['solidity-hardhat','solidity-foundry']
7576
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward']

docs/ENVS.md

+2
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ Settings for meta tags, OG tags and SEO
235235
| Variable | Type | Description | Compulsoriness | Default value | Example value | Version |
236236
| --- | --- | --- | --- | --- | --- | --- |
237237
| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Default style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` | v1.12.0+ |
238+
| 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+ |
239+
| 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+ |
238240
| 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+ |
239241
| NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` | v1.19.0+ |
240242
| 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+ |

lib/address/bech32.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { bech32 } from '@scure/base';
2+
3+
import config from 'configs/app';
4+
import bytesToHex from 'lib/bytesToHex';
5+
import hexToBytes from 'lib/hexToBytes';
6+
7+
export const DATA_PART_REGEXP = /^[\da-z]{38}$/;
8+
export const BECH_32_SEPARATOR = '1'; // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
9+
10+
export function toBech32Address(hash: string) {
11+
if (config.UI.views.address.hashFormat.bech32Prefix) {
12+
try {
13+
const words = bech32.toWords(hexToBytes(hash));
14+
return bech32.encode(config.UI.views.address.hashFormat.bech32Prefix, words);
15+
} catch (error) {}
16+
}
17+
18+
return hash;
19+
}
20+
21+
export function isBech32Address(hash: string) {
22+
if (!config.UI.views.address.hashFormat.bech32Prefix) {
23+
return false;
24+
}
25+
26+
if (!hash.startsWith(`${ config.UI.views.address.hashFormat.bech32Prefix }${ BECH_32_SEPARATOR }`)) {
27+
return false;
28+
}
29+
30+
const strippedHash = hash.replace(`${ config.UI.views.address.hashFormat.bech32Prefix }${ BECH_32_SEPARATOR }`, '');
31+
return DATA_PART_REGEXP.test(strippedHash);
32+
}
33+
34+
export function fromBech32Address(hash: string) {
35+
if (config.UI.views.address.hashFormat.bech32Prefix) {
36+
try {
37+
const { words, prefix } = bech32.decode(hash as `${ string }${ typeof BECH_32_SEPARATOR }${ string }`);
38+
39+
if (prefix !== config.UI.views.address.hashFormat.bech32Prefix) {
40+
return hash;
41+
}
42+
43+
const bytes = bech32.fromWords(words);
44+
return bytesToHex(bytes);
45+
} catch (error) {}
46+
}
47+
48+
return hash;
49+
}

lib/blob/guessDataType.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import hexToBytes from 'lib/hexToBytes';
55
import removeNonSignificantZeroBytes from './removeNonSignificantZeroBytes';
66

77
export default function guessDataType(data: string) {
8-
const bytes = new Uint8Array(hexToBytes(data));
8+
const bytes = hexToBytes(data);
99
const filteredBytes = removeNonSignificantZeroBytes(bytes);
1010

1111
return filetype(filteredBytes)[0];

lib/bytesToHex.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function bytesToBase64(bytes: Uint8Array) {
2+
let result = '';
3+
for (const byte of bytes) {
4+
result += Number(byte).toString(16).padStart(2, '0');
5+
}
6+
7+
return `0x${ result }`;
8+
}

lib/contexts/settings.tsx

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from 'react';
2+
3+
import { ADDRESS_FORMATS, type AddressFormat } from 'types/views/address';
4+
5+
import * as cookies from 'lib/cookies';
6+
7+
import { useAppContext } from './app';
8+
9+
interface SettingsProviderProps {
10+
children: React.ReactNode;
11+
}
12+
13+
interface TSettingsContext {
14+
addressFormat: AddressFormat;
15+
toggleAddressFormat: () => void;
16+
}
17+
18+
export const SettingsContext = React.createContext<TSettingsContext | null>(null);
19+
20+
export function SettingsContextProvider({ children }: SettingsProviderProps) {
21+
const { cookies: appCookies } = useAppContext();
22+
const initialAddressFormat = cookies.get(cookies.NAMES.ADDRESS_FORMAT, appCookies);
23+
24+
const [ addressFormat, setAddressFormat ] = React.useState<AddressFormat>(
25+
initialAddressFormat && ADDRESS_FORMATS.includes(initialAddressFormat) ? initialAddressFormat as AddressFormat : 'base16',
26+
);
27+
28+
const toggleAddressFormat = React.useCallback(() => {
29+
setAddressFormat(prev => {
30+
const nextValue = prev === 'base16' ? 'bech32' : 'base16';
31+
cookies.set(cookies.NAMES.ADDRESS_FORMAT, nextValue);
32+
return nextValue;
33+
});
34+
}, []);
35+
36+
const value = React.useMemo(() => {
37+
return {
38+
addressFormat,
39+
toggleAddressFormat,
40+
};
41+
}, [ addressFormat, toggleAddressFormat ]);
42+
43+
return (
44+
<SettingsContext.Provider value={ value }>
45+
{ children }
46+
</SettingsContext.Provider>
47+
);
48+
}
49+
50+
export function useSettingsContext(disabled?: boolean) {
51+
const context = React.useContext(SettingsContext);
52+
if (context === undefined || disabled) {
53+
return null;
54+
}
55+
return context;
56+
}

lib/cookies.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum NAMES {
1111
COLOR_MODE='chakra-ui-color-mode',
1212
COLOR_MODE_HEX='chakra-ui-color-mode-hex',
1313
ADDRESS_IDENTICON_TYPE='address_identicon_type',
14+
ADDRESS_FORMAT='address_format',
1415
INDEXING_ALERT='indexing_alert',
1516
ADBLOCK_DETECTED='adblock_detected',
1617
MIXPANEL_DEBUG='_mixpanel_debug',

lib/hexToBase64.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import bytesToBase64 from './bytesToBase64';
22
import hexToBytes from './hexToBytes';
33

44
export default function hexToBase64(hex: string) {
5-
const bytes = new Uint8Array(hexToBytes(hex));
5+
const bytes = hexToBytes(hex);
66

77
return bytesToBase64(bytes);
88
}

lib/hexToBytes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ export default function hexToBytes(hex: string) {
55
for (let c = startIndex; c < hex.length; c += 2) {
66
bytes.push(parseInt(hex.substring(c, c + 2), 16));
77
}
8-
return bytes;
8+
return new Uint8Array(bytes);
99
}

lib/hexToUtf8.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import hexToBytes from 'lib/hexToBytes';
22

33
export default function hexToUtf8(hex: string) {
44
const utf8decoder = new TextDecoder();
5-
const bytes = new Uint8Array(hexToBytes(hex));
5+
const bytes = hexToBytes(hex);
66

77
return utf8decoder.decode(bytes);
88
}

middleware.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export function middleware(req: NextRequest) {
2222
const res = NextResponse.next();
2323

2424
middlewares.colorTheme(req, res);
25+
middlewares.addressFormat(req, res);
2526

2627
const end = Date.now();
2728

nextjs/middlewares/addressFormat.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { NextRequest, NextResponse } from 'next/server';
2+
3+
import type { AddressFormat } from 'types/views/address';
4+
5+
import config from 'configs/app';
6+
import * as cookiesLib from 'lib/cookies';
7+
8+
export default function addressFormatMiddleware(req: NextRequest, res: NextResponse) {
9+
const addressFormatCookie = req.cookies.get(cookiesLib.NAMES.ADDRESS_FORMAT);
10+
const defaultFormat = config.UI.views.address.hashFormat.availableFormats[0];
11+
12+
if (addressFormatCookie) {
13+
const isValidCookie = config.UI.views.address.hashFormat.availableFormats.includes(addressFormatCookie.value as AddressFormat);
14+
if (!isValidCookie) {
15+
res.cookies.set(cookiesLib.NAMES.ADDRESS_FORMAT, defaultFormat, { path: '/' });
16+
}
17+
} else {
18+
res.cookies.set(cookiesLib.NAMES.ADDRESS_FORMAT, defaultFormat, { path: '/' });
19+
}
20+
}

nextjs/middlewares/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { account } from './account';
22
export { default as colorTheme } from './colorTheme';
3+
export { default as addressFormat } from './addressFormat';

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"@opentelemetry/sdk-node": "0.49.1",
5858
"@opentelemetry/sdk-trace-node": "1.22.0",
5959
"@opentelemetry/semantic-conventions": "1.22.0",
60+
"@scure/base": "1.1.9",
6061
"@sentry/cli": "^2.21.2",
6162
"@sentry/react": "7.24.0",
6263
"@sentry/tracing": "7.24.0",

pages/_app.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ChakraProvider } from 'lib/contexts/chakra';
1515
import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
1616
import { RewardsContextProvider } from 'lib/contexts/rewards';
1717
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
18+
import { SettingsContextProvider } from 'lib/contexts/settings';
1819
import { growthBook } from 'lib/growthbook/init';
1920
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
2021
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
@@ -73,8 +74,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
7374
<SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }>
7475
<RewardsContextProvider>
7576
<MarketplaceContextProvider>
76-
{ getLayout(<Component { ...pageProps }/>) }
77-
{ config.features.rewards.isEnabled && <RewardsLoginModal/> }
77+
<SettingsContextProvider>
78+
{ getLayout(<Component { ...pageProps }/>) }
79+
{ config.features.rewards.isEnabled && <RewardsLoginModal/> }
80+
</SettingsContextProvider>
7881
</MarketplaceContextProvider>
7982
</RewardsContextProvider>
8083
</SocketProvider>

playwright/TestApp.tsx

+10-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import config from 'configs/app';
1212
import { AppContextProvider } from 'lib/contexts/app';
1313
import { MarketplaceContext } from 'lib/contexts/marketplace';
1414
import { RewardsContextProvider } from 'lib/contexts/rewards';
15+
import { SettingsContextProvider } from 'lib/contexts/settings';
1516
import { SocketProvider } from 'lib/socket/context';
1617
import currentChain from 'lib/web3/currentChain';
1718
import theme from 'theme/theme';
@@ -76,13 +77,15 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext, marketp
7677
<SocketProvider url={ withSocket ? `ws://${ config.app.host }:${ socketPort }` : undefined }>
7778
<AppContextProvider { ...appContext }>
7879
<MarketplaceContext.Provider value={ marketplaceContext }>
79-
<GrowthBookProvider>
80-
<WagmiProvider config={ wagmiConfig }>
81-
<RewardsContextProvider>
82-
{ children }
83-
</RewardsContextProvider>
84-
</WagmiProvider>
85-
</GrowthBookProvider>
80+
<SettingsContextProvider>
81+
<GrowthBookProvider>
82+
<WagmiProvider config={ wagmiConfig }>
83+
<RewardsContextProvider>
84+
{ children }
85+
</RewardsContextProvider>
86+
</WagmiProvider>
87+
</GrowthBookProvider>
88+
</SettingsContextProvider>
8689
</MarketplaceContext.Provider>
8790
</AppContextProvider>
8891
</SocketProvider>

playwright/fixtures/mockEnvs.ts

+4
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,8 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
8484
rewardsService: [
8585
[ 'NEXT_PUBLIC_REWARDS_SERVICE_API_HOST', 'http://localhost:3003' ],
8686
],
87+
addressBech32Format: [
88+
[ 'NEXT_PUBLIC_ADDRESS_FORMAT', '["bech32","base16"]' ],
89+
[ 'NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX', 'tom' ],
90+
],
8791
};

types/views/address.ts

+3
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ export const ADDRESS_VIEWS_IDS = [
1414
] as const;
1515

1616
export type AddressViewId = ArrayElement<typeof ADDRESS_VIEWS_IDS>;
17+
18+
export const ADDRESS_FORMATS = [ 'base16', 'bech32' ] as const;
19+
export type AddressFormat = typeof ADDRESS_FORMATS[ number ];

ui/address/AddressDetails.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity';
1818
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
1919
import TxEntity from 'ui/shared/entities/tx/TxEntity';
2020

21+
import AddressAlternativeFormat from './details/AddressAlternativeFormat';
2122
import AddressBalance from './details/AddressBalance';
2223
import AddressImplementations from './details/AddressImplementations';
2324
import AddressNameInfo from './details/AddressNameInfo';
@@ -98,6 +99,8 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
9899
rowGap={{ base: 1, lg: 3 }}
99100
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
100101
>
102+
<AddressAlternativeFormat isLoading={ addressQuery.isPlaceholderData } addressHash={ addressHash }/>
103+
101104
{ data.filecoin?.id && (
102105
<>
103106
<DetailsInfoItem.Label
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)