diff --git a/cypress/e2e/pages/create_tx.pages.js b/cypress/e2e/pages/create_tx.pages.js index 52157d521f..b657939bac 100644 --- a/cypress/e2e/pages/create_tx.pages.js +++ b/cypress/e2e/pages/create_tx.pages.js @@ -12,7 +12,7 @@ const rotateLeftIcon = '[data-testid="RotateLeftIcon"]' const viewTransactionBtn = 'View transaction' const transactionDetailsTitle = 'Transaction details' const QueueLabel = 'needs to be executed first' -const TransactionSummary = 'Send-' +const TransactionSummary = 'Send' const maxAmountBtnStr = 'Max' const nextBtnStr = 'Next' diff --git a/cypress/e2e/pages/dashboard.pages.js b/cypress/e2e/pages/dashboard.pages.js index a2d90a3984..e8a10fffb3 100644 --- a/cypress/e2e/pages/dashboard.pages.js +++ b/cypress/e2e/pages/dashboard.pages.js @@ -45,8 +45,10 @@ export function verifyTxQueueWidget() { cy.contains(noTransactionStr).should('not.exist') // Queued txns - cy.contains(`a[href^="/transactions/tx?id=multisig_0x"]`, '13' + 'Send' + '-0.00002 GOR' + '1/1').should('exist') - + cy.contains( + `a[href^="/transactions/tx?id=multisig_0x"]`, + '13' + 'Send' + '0.00002 GOR' + 'to' + 'gor:0xE297...9665' + '1/1', + ).should('exist') cy.contains(`a[href="${constants.transactionQueueUrl}${encodeURIComponent(constants.TEST_SAFE)}"]`, viewAllStr) }) } diff --git a/cypress/e2e/smoke/tx_history.cy.js b/cypress/e2e/smoke/tx_history.cy.js index 023802aef0..8f61bcf5fc 100644 --- a/cypress/e2e/smoke/tx_history.cy.js +++ b/cypress/e2e/smoke/tx_history.cy.js @@ -1,7 +1,7 @@ import * as constants from '../../support/constants' -const INCOMING = 'Received' -const OUTGOING = 'Sent' +const INCOMING = 'Receive' +const OUTGOING = 'Send' const CONTRACT_INTERACTION = 'Contract interaction' describe('Transaction history', () => { @@ -31,8 +31,8 @@ describe('Transaction history', () => { .last() .within(() => { // Type - cy.get('img').should('have.attr', 'alt', INCOMING) - cy.contains('div', 'Received').should('exist') + cy.get('img').should('have.attr', 'alt', 'Received') + cy.contains('div', INCOMING).should('exist') // Info cy.get('img[alt="GOR"]').should('be.visible') @@ -73,10 +73,12 @@ describe('Transaction history', () => { // Type // TODO: update next line after fixing the logo // cy.find('img').should('have.attr', 'src').should('include', WRAPPED_ETH) - cy.contains('div', 'Wrapped Ether').should('exist') + cy.contains('div', 'WETH').should('exist') + + cy.contains('div', 'unlimited').should('exist') // Info - cy.contains('div', 'approve').should('exist') + cy.contains('div', 'Approve').should('exist') // Time cy.contains('span', '5:00 PM').should('exist') @@ -103,11 +105,11 @@ describe('Transaction history', () => { .prev() .within(() => { // Type - cy.get('img').should('have.attr', 'alt', OUTGOING) - cy.contains('div', 'Sent').should('exist') + cy.get('img').should('have.attr', 'alt', 'Sent') + cy.contains('div', 'Send').should('exist') // Info - cy.contains('span', '-0.11 WETH').should('exist') + cy.contains('span', '0.11 WETH').should('exist') // Time cy.contains('span', '5:01 PM').should('exist') @@ -119,7 +121,7 @@ describe('Transaction history', () => { .prev() .within(() => { // Type - cy.contains('div', 'Received').should('exist') + cy.contains('div', INCOMING).should('exist') // Info cy.contains('span', '120,497.61 DAI').should('exist') diff --git a/jest.config.cjs b/jest.config.cjs index 76962d43aa..bd350fb152 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -15,6 +15,9 @@ const customJestConfig = { }, testEnvironment: 'jest-environment-jsdom', testEnvironmentOptions: { url: 'http://localhost/balances?safe=rin:0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A' }, + globals: { + fetch: global.fetch + } } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/package.json b/package.json index eea885d6c4..97bf64ec71 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@safe-global/safe-core-sdk-utils": "^1.7.4", "@safe-global/safe-deployments": "1.25.0", "@safe-global/safe-ethers-lib": "^1.9.4", - "@safe-global/safe-gateway-typescript-sdk": "^3.9.0", + "@safe-global/safe-gateway-typescript-sdk": "^3.12.0", "@safe-global/safe-modules-deployments": "^1.0.0", "@safe-global/safe-react-components": "^2.0.6", "@sentry/react": "^7.28.1", diff --git a/src/components/dashboard/PendingTxs/PendingTxListItem.tsx b/src/components/dashboard/PendingTxs/PendingTxListItem.tsx index a700f8e9b3..2ae589338d 100644 --- a/src/components/dashboard/PendingTxs/PendingTxListItem.tsx +++ b/src/components/dashboard/PendingTxs/PendingTxListItem.tsx @@ -11,6 +11,7 @@ import TxType from '@/components/transactions/TxType' import css from './styles.module.css' import OwnersIcon from '@/public/images/common/owners.svg' import { AppRoutes } from '@/config/routes' +import { TransactionInfoType } from '@safe-global/safe-gateway-typescript-sdk' type PendingTxType = { transaction: TransactionSummary @@ -31,6 +32,8 @@ const PendingTx = ({ transaction }: PendingTxType): ReactElement => { [router, id], ) + const displayInfo = !transaction.txInfo.richDecodedInfo && transaction.txInfo.type !== TransactionInfoType.TRANSFER + return ( @@ -40,9 +43,11 @@ const PendingTx = ({ transaction }: PendingTxType): ReactElement => { - - - + {displayInfo && ( + + + + )} {isMultisigExecutionInfo(transaction.executionInfo) ? ( diff --git a/src/components/transactions/HumanDescription/index.tsx b/src/components/transactions/HumanDescription/index.tsx new file mode 100644 index 0000000000..ff6feee1d9 --- /dev/null +++ b/src/components/transactions/HumanDescription/index.tsx @@ -0,0 +1,73 @@ +import { + type RichAddressFragment, + type RichDecodedInfo, + type RichTokenValueFragment, + RichFragmentType, +} from '@safe-global/safe-gateway-typescript-sdk/dist/types/human-description' +import EthHashInfo from '@/components/common/EthHashInfo' +import css from './styles.module.css' +import useAddressBook from '@/hooks/useAddressBook' +import TokenAmount from '@/components/common/TokenAmount' +import React from 'react' +import { type Transfer } from '@safe-global/safe-gateway-typescript-sdk' +import { TransferTx } from '@/components/transactions/TxInfo' +import { formatAmount } from '@/utils/formatNumber' + +const AddressFragment = ({ fragment }: { fragment: RichAddressFragment }) => { + const addressBook = useAddressBook() + + return ( +
+ +
+ ) +} + +const TokenValueFragment = ({ fragment }: { fragment: RichTokenValueFragment }) => { + const isUnlimitedApproval = fragment.value === 'unlimited' + + return ( + + ) +} + +export const TransferDescription = ({ txInfo, isSendTx }: { txInfo: Transfer; isSendTx: boolean }) => { + const action = isSendTx ? 'Send' : 'Receive' + const direction = isSendTx ? 'to' : 'from' + const address = isSendTx ? txInfo.recipient.value : txInfo.sender.value + const name = isSendTx ? txInfo.recipient.name : txInfo.sender.name + + return ( + <> + {action} + + {direction} +
+ +
+ + ) +} + +export const HumanDescription = ({ fragments }: RichDecodedInfo) => { + return ( + <> + {fragments.map((fragment) => { + switch (fragment.type) { + case RichFragmentType.Text: + return {fragment.value} + case RichFragmentType.Address: + return + case RichFragmentType.TokenValue: + return + } + })} + + ) +} diff --git a/src/components/transactions/HumanDescription/styles.module.css b/src/components/transactions/HumanDescription/styles.module.css new file mode 100644 index 0000000000..bc6ec61307 --- /dev/null +++ b/src/components/transactions/HumanDescription/styles.module.css @@ -0,0 +1,30 @@ +.summary { + display: flex; + gap: 8px; + align-items: center; + width: 100%; +} + +/* TODO: This is a workaround to hide address in case there is a title */ +.address div[title] + div { + display: none; +} + +.value { + display: flex; + align-items: center; + font-weight: bold; + gap: 4px; +} + +.method { + display: inline-flex; + align-items: center; + gap: 0.5em; +} + +.wrapper { + display: flex; + flex-wrap: wrap; + gap: 8px; +} diff --git a/src/components/transactions/TxSummary/index.tsx b/src/components/transactions/TxSummary/index.tsx index e516d6ca67..fb640452c6 100644 --- a/src/components/transactions/TxSummary/index.tsx +++ b/src/components/transactions/TxSummary/index.tsx @@ -1,7 +1,7 @@ import type { Palette } from '@mui/material' import { Box, CircularProgress, Typography } from '@mui/material' import type { ReactElement } from 'react' -import { type Transaction, TransactionStatus } from '@safe-global/safe-gateway-typescript-sdk' +import { type Transaction, TransactionInfoType, TransactionStatus } from '@safe-global/safe-gateway-typescript-sdk' import DateTime from '@/components/common/DateTime' import TxInfo from '@/components/transactions/TxInfo' @@ -52,15 +52,18 @@ const TxSummary = ({ item, isGrouped }: TxSummaryProps): ReactElement => { : undefined const displayConfirmations = isQueue && !!submittedConfirmations && !!requiredConfirmations + const displayInfo = !tx.txInfo.richDecodedInfo && tx.txInfo.type !== TransactionInfoType.TRANSFER return ( @@ -70,9 +73,11 @@ const TxSummary = ({ item, isGrouped }: TxSummaryProps): ReactElement => { - - - + {displayInfo && ( + + + + )} diff --git a/src/components/transactions/TxSummary/styles.module.css b/src/components/transactions/TxSummary/styles.module.css index 044a9ea0e5..c8e4d787e6 100644 --- a/src/components/transactions/TxSummary/styles.module.css +++ b/src/components/transactions/TxSummary/styles.module.css @@ -7,29 +7,30 @@ } .columnTemplate { - grid-template-columns: minmax(50px, 0.25fr) minmax(150px, 2fr) minmax(150px, 2fr) minmax(200px, 2fr) 1fr 1fr minmax( - 170px, - 1fr - ); + grid-template-columns: + minmax(50px, 0.25fr) minmax(150px, 2fr) minmax(150px, 2fr) minmax(200px, 2fr) 1fr minmax(60px, 0.5fr) + minmax(170px, 1fr); grid-template-areas: 'nonce type info date confirmations actions status'; } -.columnTemplateWithoutNonce { - grid-template-columns: minmax(50px, 0.25fr) minmax(150px, 2fr) minmax(150px, 2fr) minmax(200px, 2fr) 1fr 1fr minmax( +.columnTemplateShort { + grid-template-columns: minmax(50px, 0.25fr) minmax(150px, 4fr) minmax(200px, 2fr) 1fr minmax(60px, 0.5fr) minmax( 170px, 1fr ); - grid-template-areas: 'nonce type info date confirmations actions status'; + grid-template-areas: 'nonce type date confirmations actions status'; } .columnTemplateTxHistory { - grid-template-columns: minmax(50px, 0.25fr) minmax(150px, 2fr) minmax(150px, 2fr) minmax(100px, 1fr) minmax( - 170px, - 1fr - ); + grid-template-columns: minmax(50px, 0.25fr) minmax(150px, 3fr) minmax(150px, 3fr) 0.75fr 0.5fr; grid-template-areas: 'nonce type info date status'; } +.columnTemplateTxHistoryShort { + grid-template-columns: minmax(50px, 0.25fr) 6fr 0.75fr 0.5fr; + grid-template-areas: 'nonce type date status'; +} + .columnWrap { white-space: normal; } @@ -43,7 +44,8 @@ gap: var(--space-1); } - .columnTemplate { + .columnTemplate, + .columnTemplateShort { grid-template-columns: repeat(12, auto); grid-template-areas: 'nonce type type type type type type type type type type type' @@ -54,18 +56,8 @@ 'empty actions actions actions actions actions actions actions actions actions actions actions'; } - .columnTemplateWithoutNonce { - grid-template-columns: repeat(12, 1fr); - grid-template-areas: - 'nonce type type type type type type type type type type type' - 'empty info info info info info info info info info info info' - 'empty date date date date date date date date date date date' - 'empty confirmations confirmations confirmations confirmations confirmations confirmations confirmations confirmations confirmations confirmations confirmations' - 'empty status status status status status status status status status status status' - 'empty actions actions actions actions actions actions actions actions actions actions actions'; - } - - .columnTemplateTxHistory { + .columnTemplateTxHistory, + .columnTemplateTxHistoryShort { grid-template-columns: repeat(12, 1fr); grid-template-areas: 'nonce type type type type type type type type type type type' diff --git a/src/components/transactions/TxType/index.tsx b/src/components/transactions/TxType/index.tsx index 2fbf4c053d..a1c0e64ff6 100644 --- a/src/components/transactions/TxType/index.tsx +++ b/src/components/transactions/TxType/index.tsx @@ -1,8 +1,10 @@ import { useTransactionType } from '@/hooks/useTransactionType' import type { TransactionSummary } from '@safe-global/safe-gateway-typescript-sdk' +import { TransactionInfoType, TransferDirection } from '@safe-global/safe-gateway-typescript-sdk' import { Box } from '@mui/material' import css from './styles.module.css' import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard' +import { HumanDescription, TransferDescription } from '@/components/transactions/HumanDescription' type TxTypeProps = { tx: TransactionSummary @@ -11,6 +13,8 @@ type TxTypeProps = { const TxType = ({ tx }: TxTypeProps) => { const type = useTransactionType(tx) + const humanDescription = tx.txInfo.richDecodedInfo?.fragments + return ( { height={16} fallback="/images/transactions/custom.svg" /> - {type.text} + {humanDescription ? ( + + ) : tx.txInfo.type === TransactionInfoType.TRANSFER ? ( + + ) : ( + type.text + )} ) } diff --git a/src/components/transactions/TxType/styles.module.css b/src/components/transactions/TxType/styles.module.css index 17d5f0d140..2a8aa827b3 100644 --- a/src/components/transactions/TxType/styles.module.css +++ b/src/components/transactions/TxType/styles.module.css @@ -1,6 +1,7 @@ .txType { display: flex; align-items: center; + flex-wrap: wrap; gap: var(--space-1); color: var(--color-text-primary); } diff --git a/yarn.lock b/yarn.lock index 22db38c51a..fc3e6dd9ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3286,7 +3286,12 @@ "@safe-global/safe-core-sdk-utils" "^1.7.4" ethers "5.7.2" -"@safe-global/safe-gateway-typescript-sdk@^3.5.3", "@safe-global/safe-gateway-typescript-sdk@^3.9.0": +"@safe-global/safe-gateway-typescript-sdk@^3.12.0": + version "3.12.0" + resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.12.0.tgz#aa767a32f4d10f4ec9a47ad7e32d547d3b51e94c" + integrity sha512-hExCo62lScVC9/ztVqYEYL2pFxcqLTvB8fj0WtdP5FWrvbtEgD0pbVolchzD5bf85pbzvEwdAxSVS7EdCZxTNw== + +"@safe-global/safe-gateway-typescript-sdk@^3.5.3": version "3.10.0" resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.10.0.tgz#a252ac5a61487d7785c44f1ed7e899ccd5aa9038" integrity sha512-nhWjFRRgrGz4uZbyQ3Hgm4si1AixCWlnvi5WUCq/+V+e8EoA2Apj9xJEt8zzXvtELlddFqkH2sfTFy9LIjGXKg==