diff --git a/ui/tx/assetFlows/components/NovesSubHeadingInterpretation.tsx b/ui/tx/assetFlows/components/NovesSubHeadingInterpretation.tsx index 7cbd42457c..6270ad2cd2 100644 --- a/ui/tx/assetFlows/components/NovesSubHeadingInterpretation.tsx +++ b/ui/tx/assetFlows/components/NovesSubHeadingInterpretation.tsx @@ -4,6 +4,7 @@ import React, { Fragment } from 'react'; import type { NovesResponseData } from 'types/api/noves'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import IconSvg from 'ui/shared/IconSvg'; import { getDescriptionItems } from 'ui/tx/assetFlows/utils/getDescriptionItems'; @@ -61,7 +62,17 @@ const NovesSubHeadingInterpretation: FC = ({ data, isLoading }) => { fontSize="lg" w="fit-content" /> - ) } + ) + } + { + item.address && ( + + ) + } )) } diff --git a/ui/tx/assetFlows/utils/getDescriptionItems.test.ts b/ui/tx/assetFlows/utils/getDescriptionItems.test.ts index 5685b64d44..715a786f4c 100644 --- a/ui/tx/assetFlows/utils/getDescriptionItems.test.ts +++ b/ui/tx/assetFlows/utils/getDescriptionItems.test.ts @@ -7,9 +7,28 @@ it('creates sub heading items to render', async() => { expect(result).toEqual([ { - text: ' Called function \'stake\' on contract 0xef326CdAdA59D3A740A76bB5f4F88Fb2', token: undefined, + text: ' ', hasId: false, + type: 'action', + actionText: 'Called function', + address: undefined, + }, + { + token: undefined, + text: '\'stake\' ', + hasId: false, + type: 'action', + actionText: 'on contract', + address: undefined, + }, + { + token: undefined, + text: '', + hasId: false, + type: 'contract', + actionText: undefined, + address: '0xef326cdada59d3a740a76bb5f4f88fb2f1076164', }, ]); }); diff --git a/ui/tx/assetFlows/utils/getDescriptionItems.ts b/ui/tx/assetFlows/utils/getDescriptionItems.ts index d8a9a2e8b0..d252aea98d 100644 --- a/ui/tx/assetFlows/utils/getDescriptionItems.ts +++ b/ui/tx/assetFlows/utils/getDescriptionItems.ts @@ -10,7 +10,7 @@ interface TokenWithIndices { hasId: boolean; indices: Array; token?: NovesTokenInfo; - type?: 'action'; + type?: 'action' | 'contract'; } export interface DescriptionItems { @@ -19,8 +19,11 @@ export interface DescriptionItems { hasId: boolean | undefined; type?: string; actionText?: string; + address?: string; } +const CONTRACT_REGEXP = /(0x[\da-fA-F]{32}\b)/g; + export const getDescriptionItems = (translateData: NovesResponseData): Array => { // Remove final dot and add space at the start to avoid matching issues @@ -33,9 +36,11 @@ export const getDescriptionItems = (translateData: NovesResponseData): Array parsedDescription.toUpperCase().includes(` ${ name.toUpperCase() }`)); let tokensMatchedBySymbol = tokenData.symbolList.filter(symbol => parsedDescription.toUpperCase().includes(` ${ symbol.toUpperCase() }`)); - const actions = [ 'sent', 'Sent', 'Called function', 'called function', 'on contract' ]; + const actions = [ 'sent', 'Sent', 'Called function', 'called function', 'on contract', 'swap', 'Swap' ]; const actionsMatched = actions.filter(action => parsedDescription.includes(action)); + const contractMatched = parsedDescription.match(CONTRACT_REGEXP) || []; + // Filter symbols if they're already matched by name tokensMatchedBySymbol = tokensMatchedBySymbol.filter(symbol => !tokensMatchedByName.includes(tokenData.bySymbol[symbol]?.name || '')); @@ -44,6 +49,7 @@ export const getDescriptionItems = (translateData: NovesResponseData): Array indices.push(...i.indices)); } + if (contractMatched.length) { + tokensByContract = parseTokensByContract(contractMatched, parsedDescription, translateData); + + tokensByContract.forEach(i => indices.push(...i.indices)); + } + const indicesSorted = _.uniq(indices.sort((a, b) => a - b)); - const tokensWithIndices = _.uniqBy(_.concat(tokensByName, tokensBySymbol, tokensByAction), 'name'); + const tokensWithIndices = _.uniqBy(_.concat(tokensByName, tokensBySymbol, tokensByAction, tokensByContract), 'name'); return createDescriptionItems(indicesSorted, tokensWithIndices, parsedDescription); }; @@ -146,7 +158,7 @@ const parseTokensBySymbol = (tokensMatchedBySymbol: Array, idsMatched: A }; const parseTokensByAction = (actionsMatched: Array, parsedDescription: string) => { - const tokensBySymbol: Array = actionsMatched.map(action => { + const tokensByAction: Array = actionsMatched.map(action => { return { name: action, indices: [ ...parsedDescription.matchAll(new RegExp(action, 'gi')) ].map(a => a.index) as unknown as Array, @@ -155,7 +167,25 @@ const parseTokensByAction = (actionsMatched: Array, parsedDescription: s }; }); - return tokensBySymbol; + return tokensByAction; +}; + +const parseTokensByContract = (contractMatched: Array, parsedDescription: string, translateData: NovesResponseData) => { + const toAddress = translateData.rawTransactionData.toAddress.toLowerCase(); + const contractFiltered = contractMatched.filter(contract => toAddress.startsWith(contract.toLowerCase()))[0]; + + if (!contractFiltered) { + return []; + } + + const tokensByContract: Array = [ { + name: toAddress, + indices: [ ...parsedDescription.matchAll(new RegExp(contractFiltered, 'gi')) ].map(a => a.index) as unknown as Array, + hasId: false, + type: 'contract', + } ]; + + return tokensByContract; }; const createDescriptionItems = (indicesSorted: Array, tokensWithIndices: Array, parsedDescription: string) => { @@ -167,26 +197,30 @@ const createDescriptionItems = (indicesSorted: Array, tokensWithIndices: if (i === 0) { const isAction = item?.type === 'action'; + const isContract = item?.type === 'contract'; token = { token: item?.token, text: parsedDescription.substring(0, endIndex), hasId: item?.hasId, - type: isAction ? 'action' : undefined, + type: item?.type, actionText: isAction ? item.name : undefined, + address: isContract ? item.name : undefined, }; } else { const previousItem = tokensWithIndices.find(t => t?.indices.includes(indicesSorted[i - 1])); // Add the length of the text of the previous token to remove it from the start const startIndex = indicesSorted[i - 1] + (previousItem?.name.length || 0) + 1; const isAction = item?.type === 'action'; + const isContract = item?.type === 'contract'; token = { token: item?.token, text: parsedDescription.substring(startIndex, endIndex), hasId: item?.hasId, - type: isAction ? 'action' : undefined, + type: item?.type, actionText: isAction ? item.name : undefined, + address: isContract ? item.name : undefined, }; } @@ -199,7 +233,7 @@ const createDescriptionItems = (indicesSorted: Array, tokensWithIndices: // Check if there is text left after the last token and push it to the array if (restString) { - descriptionItems.push({ text: restString, token: undefined, hasId: false, type: undefined, actionText: undefined }); + descriptionItems.push({ text: restString, token: undefined, hasId: false, type: undefined, actionText: undefined, address: undefined }); } return descriptionItems;