mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Metadata: send address to fetchers once visited (#2814)
* Metadata: send address to fetchers once visited Fixes #2723 * fix ts
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import type { Feature } from './types';
|
||||
|
||||
import apis from '../apis';
|
||||
import { getEnvValue } from '../utils';
|
||||
|
||||
const title = 'Address metadata';
|
||||
|
||||
const config: Feature<{}> = (() => {
|
||||
const config: Feature<{ isAddressTagsUpdateEnabled: boolean }> = (() => {
|
||||
if (apis.metadata) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
isAddressTagsUpdateEnabled: getEnvValue('NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED') !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -657,6 +657,25 @@ const bridgedTokensSchema = yup
|
||||
}),
|
||||
});
|
||||
|
||||
const addressMetadataSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST: yup
|
||||
.string()
|
||||
.test(urlTest),
|
||||
NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED: yup
|
||||
.boolean()
|
||||
.when('NEXT_PUBLIC_METADATA_SERVICE_API_HOST', {
|
||||
is: (value: string) => Boolean(value),
|
||||
then: (schema) => schema,
|
||||
otherwise: (schema) => schema.test(
|
||||
'not-exist',
|
||||
'NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED cannot not be used if NEXT_PUBLIC_METADATA_SERVICE_API_HOST is not defined',
|
||||
value => value === undefined,
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const deFiDropdownItemSchema: yup.ObjectSchema<DeFiDropdownItem> = yup
|
||||
.object({
|
||||
text: yup.string().required(),
|
||||
@@ -943,7 +962,6 @@ const schema = yup
|
||||
NEXT_PUBLIC_VISUALIZE_API_BASE_PATH: yup.string(),
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest),
|
||||
NEXT_PUBLIC_NAME_SERVICE_API_HOST: yup.string().test(urlTest),
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST: yup.string().test(urlTest),
|
||||
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup.string().test(urlTest),
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup
|
||||
.mixed()
|
||||
@@ -1098,6 +1116,7 @@ const schema = yup
|
||||
.concat(beaconChainSchema)
|
||||
.concat(bridgedTokensSchema)
|
||||
.concat(sentrySchema)
|
||||
.concat(tacSchema);
|
||||
.concat(tacSchema)
|
||||
.concat(addressMetadataSchema);
|
||||
|
||||
export default schema;
|
||||
|
||||
@@ -7,3 +7,5 @@ NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo
|
||||
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
|
||||
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=['https://example.com','https://example2.com']
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://example.com
|
||||
NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED=false
|
||||
@@ -657,6 +657,7 @@ This feature allows name tags and other public tags for addresses.
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_METADATA_SERVICE_API_HOST | `string` | Metadata Service API endpoint url | Required | - | `https://metadata.services.blockscout.com` | v1.30.0+ |
|
||||
| NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED | `boolean` | Enables requests to the Metadata Service to schedule an update for address tags after the user visits the address page in the app. | - | `true` | `false` | v2.2.0+ |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressCounters } from 'types/api/address';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
|
||||
const feature = config.features.addressMetadata;
|
||||
|
||||
interface Params {
|
||||
address: string | undefined;
|
||||
counters: AddressCounters | undefined;
|
||||
isEnabled: boolean;
|
||||
}
|
||||
|
||||
const TXS_THRESHOLD = 500;
|
||||
|
||||
export default function useAddressMetadataInitUpdate({ address, counters, isEnabled }: Params) {
|
||||
|
||||
const apiFetch = useApiFetch();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
feature.isEnabled &&
|
||||
feature.isAddressTagsUpdateEnabled &&
|
||||
address &&
|
||||
isEnabled &&
|
||||
counters?.transactions_count && Number(counters.transactions_count) > TXS_THRESHOLD
|
||||
) {
|
||||
apiFetch('metadata:address_submit', {
|
||||
fetchParams: {
|
||||
method: 'POST',
|
||||
body: {
|
||||
addresses: [ address ],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [ address, apiFetch, counters?.transactions_count, isEnabled ]);
|
||||
}
|
||||
@@ -11,6 +11,9 @@ export const METADATA_API_RESOURCES = {
|
||||
public_tag_types: {
|
||||
path: '/api/v1/public-tag-types',
|
||||
},
|
||||
address_submit: {
|
||||
path: '/api/v1/addresses\\:submit',
|
||||
},
|
||||
} satisfies Record<string, ApiResource>;
|
||||
|
||||
export type MetadataApiResourceName = `metadata:${ keyof typeof METADATA_API_RESOURCES }`;
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as pwConfig from 'playwright/utils/config';
|
||||
|
||||
import AddressDetails from './AddressDetails';
|
||||
import MockAddressPage from './testUtils/MockAddressPage';
|
||||
import type { AddressCountersQuery } from './utils/useAddressCountersQuery';
|
||||
import type { AddressQuery } from './utils/useAddressQuery';
|
||||
|
||||
const ADDRESS_HASH = addressMock.hash;
|
||||
@@ -22,9 +23,14 @@ test.describe('mobile', () => {
|
||||
|
||||
test('contract', async({ render, mockApiResponse, page }) => {
|
||||
await mockApiResponse('general:address', addressMock.contract, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forContract, { pathParams: { hash: ADDRESS_HASH } });
|
||||
|
||||
const component = await render(<AddressDetails addressQuery={{ data: addressMock.contract } as AddressQuery}/>, { hooksConfig });
|
||||
const component = await render(
|
||||
<AddressDetails
|
||||
addressQuery={{ data: addressMock.contract } as AddressQuery}
|
||||
countersQuery={{ data: countersMock.forContract } as AddressCountersQuery}
|
||||
/>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
@@ -34,9 +40,14 @@ test.describe('mobile', () => {
|
||||
|
||||
test('validator', async({ render, page, mockApiResponse }) => {
|
||||
await mockApiResponse('general:address', addressMock.validator, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forValidator, { pathParams: { hash: ADDRESS_HASH } });
|
||||
|
||||
const component = await render(<AddressDetails addressQuery={{ data: addressMock.validator } as AddressQuery}/>, { hooksConfig });
|
||||
const component = await render(
|
||||
<AddressDetails
|
||||
addressQuery={{ data: addressMock.validator } as AddressQuery}
|
||||
countersQuery={{ data: countersMock.forValidator } as AddressCountersQuery}
|
||||
/>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
@@ -46,9 +57,14 @@ test.describe('mobile', () => {
|
||||
|
||||
test('filecoin', async({ render, mockApiResponse, page }) => {
|
||||
await mockApiResponse('general:address', addressMock.filecoin, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forValidator, { pathParams: { hash: ADDRESS_HASH } });
|
||||
|
||||
const component = await render(<AddressDetails addressQuery={{ data: addressMock.filecoin } as AddressQuery}/>, { hooksConfig });
|
||||
const component = await render(
|
||||
<AddressDetails
|
||||
addressQuery={{ data: addressMock.filecoin } as AddressQuery}
|
||||
countersQuery={{ data: countersMock.forValidator } as AddressCountersQuery}
|
||||
/>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
@@ -59,9 +75,14 @@ test.describe('mobile', () => {
|
||||
|
||||
test('contract', async({ render, page, mockApiResponse }) => {
|
||||
await mockApiResponse('general:address', addressMock.contract, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forContract, { pathParams: { hash: ADDRESS_HASH } });
|
||||
|
||||
const component = await render(<AddressDetails addressQuery={{ data: addressMock.contract } as AddressQuery}/>, { hooksConfig });
|
||||
const component = await render(
|
||||
<AddressDetails
|
||||
addressQuery={{ data: addressMock.contract } as AddressQuery}
|
||||
countersQuery={{ data: countersMock.forContract } as AddressCountersQuery}
|
||||
/>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
@@ -72,7 +93,6 @@ test('contract', async({ render, page, mockApiResponse }) => {
|
||||
// there's an unexpected timeout occurred in this test
|
||||
test.fixme('token', async({ render, mockApiResponse, injectMetaMaskProvider, page }) => {
|
||||
await mockApiResponse('general:address', addressMock.token, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forToken, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_tokens', tokensMock.erc20List, { pathParams: { hash: ADDRESS_HASH }, queryParams: { type: 'ERC-20' }, times: 1 });
|
||||
await mockApiResponse('general:address_tokens', tokensMock.erc721List, { pathParams: { hash: ADDRESS_HASH }, queryParams: { type: 'ERC-721' }, times: 1 });
|
||||
await mockApiResponse('general:address_tokens', tokensMock.erc1155List, { pathParams: { hash: ADDRESS_HASH }, queryParams: { type: 'ERC-1155' }, times: 1 });
|
||||
@@ -81,7 +101,7 @@ test.fixme('token', async({ render, mockApiResponse, injectMetaMaskProvider, pag
|
||||
|
||||
const component = await render(
|
||||
<MockAddressPage>
|
||||
<AddressDetails addressQuery={{ data: addressMock.token } as AddressQuery}/>
|
||||
<AddressDetails addressQuery={{ data: addressMock.token } as AddressQuery} countersQuery={{ data: countersMock.forToken } as AddressCountersQuery}/>
|
||||
</MockAddressPage>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
@@ -94,9 +114,14 @@ test.fixme('token', async({ render, mockApiResponse, injectMetaMaskProvider, pag
|
||||
|
||||
test('validator', async({ render, mockApiResponse, page }) => {
|
||||
await mockApiResponse('general:address', addressMock.validator, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forValidator, { pathParams: { hash: ADDRESS_HASH } });
|
||||
|
||||
const component = await render(<AddressDetails addressQuery={{ data: addressMock.validator } as AddressQuery}/>, { hooksConfig });
|
||||
const component = await render(
|
||||
<AddressDetails
|
||||
addressQuery={{ data: addressMock.validator } as AddressQuery}
|
||||
countersQuery={{ data: countersMock.forValidator } as AddressCountersQuery}
|
||||
/>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
@@ -106,9 +131,14 @@ test('validator', async({ render, mockApiResponse, page }) => {
|
||||
|
||||
test('filecoin', async({ render, mockApiResponse, page }) => {
|
||||
await mockApiResponse('general:address', addressMock.filecoin, { pathParams: { hash: ADDRESS_HASH } });
|
||||
await mockApiResponse('general:address_counters', countersMock.forValidator, { pathParams: { hash: ADDRESS_HASH } });
|
||||
|
||||
const component = await render(<AddressDetails addressQuery={{ data: addressMock.filecoin } as AddressQuery}/>, { hooksConfig });
|
||||
const component = await render(
|
||||
<AddressDetails
|
||||
addressQuery={{ data: addressMock.filecoin } as AddressQuery}
|
||||
countersQuery={{ data: countersMock.forValidator } as AddressCountersQuery}
|
||||
/>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
|
||||
@@ -26,24 +26,20 @@ import AddressNetWorth from './details/AddressNetWorth';
|
||||
import AddressSaveOnGas from './details/AddressSaveOnGas';
|
||||
import FilecoinActorTag from './filecoin/FilecoinActorTag';
|
||||
import TokenSelect from './tokenSelect/TokenSelect';
|
||||
import useAddressCountersQuery from './utils/useAddressCountersQuery';
|
||||
import type { AddressCountersQuery } from './utils/useAddressCountersQuery';
|
||||
import type { AddressQuery } from './utils/useAddressQuery';
|
||||
|
||||
interface Props {
|
||||
addressQuery: AddressQuery;
|
||||
countersQuery: AddressCountersQuery;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const AddressDetails = ({ addressQuery, isLoading }: Props) => {
|
||||
const AddressDetails = ({ addressQuery, countersQuery, isLoading }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const addressHash = getQueryParamString(router.query.hash);
|
||||
|
||||
const countersQuery = useAddressCountersQuery({
|
||||
hash: addressHash,
|
||||
addressQuery,
|
||||
});
|
||||
|
||||
const error404Data = React.useMemo(() => ({
|
||||
hash: addressHash || '',
|
||||
is_contract: false,
|
||||
|
||||
+15
-1
@@ -8,6 +8,7 @@ import type { EntityTag } from 'ui/shared/EntityTags/types';
|
||||
import config from 'configs/app';
|
||||
import getCheckedSummedAddress from 'lib/address/getCheckedSummedAddress';
|
||||
import useAddressMetadataInfoQuery from 'lib/address/useAddressMetadataInfoQuery';
|
||||
import useAddressMetadataInitUpdate from 'lib/address/useAddressMetadataInitUpdate';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { useAppContext } from 'lib/contexts/app';
|
||||
import useAddressProfileApiQuery from 'lib/hooks/useAddressProfileApiQuery';
|
||||
@@ -42,6 +43,7 @@ import AddressMetadataAlert from 'ui/address/details/AddressMetadataAlert';
|
||||
import AddressQrCode from 'ui/address/details/AddressQrCode';
|
||||
import AddressEnsDomains from 'ui/address/ensDomains/AddressEnsDomains';
|
||||
import SolidityscanReport from 'ui/address/SolidityscanReport';
|
||||
import useAddressCountersQuery from 'ui/address/utils/useAddressCountersQuery';
|
||||
import useAddressQuery from 'ui/address/utils/useAddressQuery';
|
||||
import useCheckAddressFormat from 'ui/address/utils/useCheckAddressFormat';
|
||||
import useCheckDomainNameParam from 'ui/address/utils/useCheckDomainNameParam';
|
||||
@@ -86,6 +88,11 @@ const AddressPageContent = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useAddressCountersQuery({
|
||||
hash,
|
||||
addressQuery,
|
||||
});
|
||||
|
||||
const userOpsAccountQuery = useApiQuery('general:user_ops_account', {
|
||||
pathParams: { hash },
|
||||
queryOptions: {
|
||||
@@ -144,6 +151,12 @@ const AddressPageContent = () => {
|
||||
handler: handleFetchedBytecodeMessage,
|
||||
});
|
||||
|
||||
useAddressMetadataInitUpdate({
|
||||
address: hash,
|
||||
counters: countersQuery.data,
|
||||
isEnabled: !countersQuery.isPlaceholderData && !countersQuery.isDegradedData,
|
||||
});
|
||||
|
||||
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined);
|
||||
|
||||
const xStarQuery = useFetchXStarScore({ hash });
|
||||
@@ -159,7 +172,7 @@ const AddressPageContent = () => {
|
||||
{
|
||||
id: 'index',
|
||||
title: 'Details',
|
||||
component: <AddressDetails addressQuery={ addressQuery } isLoading={ isTabsLoading }/>,
|
||||
component: <AddressDetails addressQuery={ addressQuery } countersQuery={ countersQuery } isLoading={ isTabsLoading }/>,
|
||||
},
|
||||
addressQuery.data?.is_contract ? {
|
||||
id: 'contract',
|
||||
@@ -270,6 +283,7 @@ const AddressPageContent = () => {
|
||||
].filter(Boolean);
|
||||
}, [
|
||||
addressQuery,
|
||||
countersQuery,
|
||||
contractTabs,
|
||||
addressTabsCountersQuery.data,
|
||||
userOpsAccountQuery.data,
|
||||
|
||||
Reference in New Issue
Block a user