Commit 45b93c5c authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #2146 from blockscout/name-tag-in-tx-interpretation

Display address name and tag in tx interpretation
parents 2081c83e d3db2e9f
/* eslint-disable max-len */ /* eslint-disable max-len */
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import * as addressMock from 'mocks/address/address';
import { publicTag, privateTag, watchlistName } from 'mocks/address/tag'; import { publicTag, privateTag, watchlistName } from 'mocks/address/tag';
import * as tokenTransferMock from 'mocks/tokens/tokenTransfer'; import * as tokenTransferMock from 'mocks/tokens/tokenTransfer';
import * as decodedInputDataMock from 'mocks/txs/decodedInputData'; import * as decodedInputDataMock from 'mocks/txs/decodedInputData';
...@@ -47,7 +48,7 @@ export const base: Transaction = { ...@@ -47,7 +48,7 @@ export const base: Transaction = {
status: 'ok', status: 'ok',
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
to: { to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
implementations: null, implementations: null,
is_contract: false, is_contract: false,
is_verified: true, is_verified: true,
...@@ -110,7 +111,7 @@ export const withTokenTransfer: Transaction = { ...@@ -110,7 +111,7 @@ export const withTokenTransfer: Transaction = {
...base, ...base,
hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196', hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196',
to: { to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
implementations: null, implementations: null,
is_contract: true, is_contract: true,
is_verified: true, is_verified: true,
...@@ -166,7 +167,7 @@ export const withRawRevertReason: Transaction = { ...@@ -166,7 +167,7 @@ export const withRawRevertReason: Transaction = {
raw: '4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652e', raw: '4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652e',
}, },
to: { to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
implementations: null, implementations: null,
is_verified: true, is_verified: true,
is_contract: true, is_contract: true,
...@@ -344,7 +345,7 @@ export const base2 = { ...@@ -344,7 +345,7 @@ export const base2 = {
hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
from: { from: {
...base.from, ...base.from,
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
}, },
}; };
...@@ -353,7 +354,7 @@ export const base3 = { ...@@ -353,7 +354,7 @@ export const base3 = {
hash: '0x12d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', hash: '0x12d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
from: { from: {
...base.from, ...base.from,
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
}, },
}; };
...@@ -375,3 +376,18 @@ export const withBlob = { ...@@ -375,3 +376,18 @@ export const withBlob = {
tx_types: [ 'blob_transaction' as const ], tx_types: [ 'blob_transaction' as const ],
type: 3, type: 3,
}; };
export const withRecipientName = {
...base,
to: addressMock.withName,
};
export const withRecipientEns = {
...base,
to: addressMock.withEns,
};
export const withRecipientNameTag = {
...withRecipientEns,
to: addressMock.withNameTag,
};
import type { TxInterpretationResponse } from 'types/api/txInterpretation'; import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import { hash } from 'mocks/address/address';
export const txInterpretation: TxInterpretationResponse = { export const txInterpretation: TxInterpretationResponse = {
data: { data: {
summaries: [ { summaries: [ {
...@@ -25,7 +27,7 @@ export const txInterpretation: TxInterpretationResponse = { ...@@ -25,7 +27,7 @@ export const txInterpretation: TxInterpretationResponse = {
to_address: { to_address: {
type: 'address', type: 'address',
value: { value: {
hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF', hash: hash,
implementations: null, implementations: null,
is_contract: false, is_contract: false,
is_verified: false, is_verified: false,
......
...@@ -2,6 +2,7 @@ import { Skeleton, Tooltip, chakra } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Skeleton, Tooltip, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { AddressParam } from 'types/api/addressParams';
import type { import type {
TxInterpretationSummary, TxInterpretationSummary,
TxInterpretationVariable, TxInterpretationVariable,
...@@ -23,14 +24,14 @@ import { extractVariables, getStringChunks, fillStringVariables, checkSummary, N ...@@ -23,14 +24,14 @@ import { extractVariables, getStringChunks, fillStringVariables, checkSummary, N
type Props = { type Props = {
summary?: TxInterpretationSummary; summary?: TxInterpretationSummary;
isLoading?: boolean; isLoading?: boolean;
ensDomainNames?: Record<string, string>; addressDataMap?: Record<string, AddressParam>;
className?: string; className?: string;
} }
type NonStringTxInterpretationVariable = Exclude<TxInterpretationVariable, TxInterpretationVariableString> type NonStringTxInterpretationVariable = Exclude<TxInterpretationVariable, TxInterpretationVariableString>
const TxInterpretationElementByType = ( const TxInterpretationElementByType = (
{ variable, ensDomainNames }: { variable?: NonStringTxInterpretationVariable; ensDomainNames?: Record<string, string> }, { variable, addressDataMap }: { variable?: NonStringTxInterpretationVariable; addressDataMap?: Record<string, AddressParam> },
) => { ) => {
const onAddressClick = React.useCallback(() => { const onAddressClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' }); mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' });
...@@ -51,14 +52,10 @@ const TxInterpretationElementByType = ( ...@@ -51,14 +52,10 @@ const TxInterpretationElementByType = (
const { type, value } = variable; const { type, value } = variable;
switch (type) { switch (type) {
case 'address': { case 'address': {
let address = value;
if (!address.ens_domain_name && ensDomainNames?.[address.hash]) {
address = { ...address, ens_domain_name: ensDomainNames[address.hash] };
}
return ( return (
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}> <chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<AddressEntity <AddressEntity
address={ address } address={ addressDataMap?.[value.hash] || value }
truncation="constant" truncation="constant"
onClick={ onAddressClick } onClick={ onAddressClick }
whiteSpace="initial" whiteSpace="initial"
...@@ -129,7 +126,7 @@ const TxInterpretationElementByType = ( ...@@ -129,7 +126,7 @@ const TxInterpretationElementByType = (
} }
}; };
const TxInterpretation = ({ summary, isLoading, ensDomainNames, className }: Props) => { const TxInterpretation = ({ summary, isLoading, addressDataMap, className }: Props) => {
if (!summary) { if (!summary) {
return null; return null;
} }
...@@ -161,7 +158,7 @@ const TxInterpretation = ({ summary, isLoading, ensDomainNames, className }: Pro ...@@ -161,7 +158,7 @@ const TxInterpretation = ({ summary, isLoading, ensDomainNames, className }: Pro
( (
<TxInterpretationElementByType <TxInterpretationElementByType
variable={ variables[variablesNames[index]] as NonStringTxInterpretationVariable } variable={ variables[variablesNames[index]] as NonStringTxInterpretationVariable }
ensDomainNames={ ensDomainNames } addressDataMap={ addressDataMap }
/> />
) )
) } ) }
......
import React from 'react'; import React from 'react';
import type { AddressMetadataInfo, AddressMetadataTagApi } from 'types/api/addressMetadata'; import type { AddressMetadataInfo, AddressMetadataTagApi } from 'types/api/addressMetadata';
import type { AddressParam } from 'types/api/addressParams';
import config from 'configs/app'; import config from 'configs/app';
import { protocolTagWithMeta } from 'mocks/metadata/address'; import { protocolTagWithMeta } from 'mocks/metadata/address';
...@@ -65,22 +64,24 @@ test.describe('blockscout provider', () => { ...@@ -65,22 +64,24 @@ test.describe('blockscout provider', () => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('with interpretation and recipient ENS domain', async({ render, mockApiResponse }) => { test('with interpretation and recipient name +@mobile', async({ render, mockApiResponse }) => {
const txData = { const newTxQuery = { ...txQuery, data: txMock.withRecipientName } as TxQuery;
...txMock.base, await mockApiResponse('tx_interpretation', txInterpretation, { pathParams: { hash } });
to: { const component = await render(<TxSubHeading hash={ hash } hasTag={ false } txQuery={ newTxQuery }/>);
...txMock.base.to, await expect(component).toHaveScreenshot();
hash: (txInterpretation.data.summaries[0].summary_template_variables.to_address.value as AddressParam).hash, });
ens_domain_name: 'duckduck.eth',
}, test('with interpretation and recipient ENS domain +@mobile', async({ render, mockApiResponse }) => {
}; const newTxQuery = { ...txQuery, data: txMock.withRecipientEns } as TxQuery;
const txWithEnsQuery = { await mockApiResponse('tx_interpretation', txInterpretation, { pathParams: { hash } });
data: txData, const component = await render(<TxSubHeading hash={ hash } hasTag={ false } txQuery={ newTxQuery }/>);
isPlaceholderData: false, await expect(component).toHaveScreenshot();
isError: false, });
} as TxQuery;
test('with interpretation and recipient name tag +@mobile', async({ render, mockApiResponse }) => {
const newTxQuery = { ...txQuery, data: txMock.withRecipientNameTag } as TxQuery;
await mockApiResponse('tx_interpretation', txInterpretation, { pathParams: { hash } }); await mockApiResponse('tx_interpretation', txInterpretation, { pathParams: { hash } });
const component = await render(<TxSubHeading hash={ hash } hasTag={ false } txQuery={ txWithEnsQuery }/>); const component = await render(<TxSubHeading hash={ hash } hasTag={ false } txQuery={ newTxQuery }/>);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
......
import { Box, Flex, Link } from '@chakra-ui/react'; import { Box, Flex, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressParam } from 'types/api/addressParams';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate'; import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate';
...@@ -59,12 +61,12 @@ const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => { ...@@ -59,12 +61,12 @@ const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => {
(hasNovesInterpretation && novesInterpretationQuery.data && !novesInterpretationQuery.isPlaceholderData) || (hasNovesInterpretation && novesInterpretationQuery.data && !novesInterpretationQuery.isPlaceholderData) ||
(hasInternalInterpretation && !txInterpretationQuery.isPlaceholderData); (hasInternalInterpretation && !txInterpretationQuery.isPlaceholderData);
const ensDomainNames: Record<string, string> = {}; const addressDataMap: Record<string, AddressParam> = {};
[ txQuery.data?.from, txQuery.data?.to ].forEach(data => { [ txQuery.data?.from, txQuery.data?.to ]
if (data?.hash && data?.ens_domain_name) { .filter((data): data is AddressParam => Boolean(data && data.hash))
ensDomainNames[data.hash] = data.ens_domain_name; .forEach(data => {
} addressDataMap[data.hash] = data;
}); });
const content = (() => { const content = (() => {
if (hasNovesInterpretation && novesInterpretationQuery.data) { if (hasNovesInterpretation && novesInterpretationQuery.data) {
...@@ -73,7 +75,7 @@ const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => { ...@@ -73,7 +75,7 @@ const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => {
<TxInterpretation <TxInterpretation
summary={ novesSummary } summary={ novesSummary }
isLoading={ novesInterpretationQuery.isPlaceholderData || txQuery.isPlaceholderData } isLoading={ novesInterpretationQuery.isPlaceholderData || txQuery.isPlaceholderData }
ensDomainNames={ ensDomainNames } addressDataMap={ addressDataMap }
fontSize="lg" fontSize="lg"
mr={{ base: 0, lg: 6 }} mr={{ base: 0, lg: 6 }}
/> />
...@@ -84,7 +86,7 @@ const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => { ...@@ -84,7 +86,7 @@ const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => {
<TxInterpretation <TxInterpretation
summary={ txInterpretationQuery.data?.data.summaries[0] } summary={ txInterpretationQuery.data?.data.summaries[0] }
isLoading={ txInterpretationQuery.isPlaceholderData || txQuery.isPlaceholderData } isLoading={ txInterpretationQuery.isPlaceholderData || txQuery.isPlaceholderData }
ensDomainNames={ ensDomainNames } addressDataMap={ addressDataMap }
fontSize="lg" fontSize="lg"
mr={ hasViewAllInterpretationsLink ? 3 : 0 } mr={ hasViewAllInterpretationsLink ? 3 : 0 }
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment