From 8b1f2ae39129b88598ef852b0d02b9384e761536 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Tue, 15 Oct 2024 16:34:10 +0100 Subject: [PATCH] feat: dapp initiated token transfer --- app/_locales/en/messages.json | 3 + .../redesign/transaction-confirmation.ts | 10 ++ test/e2e/page-objects/pages/test-dapp.ts | 7 ++ .../erc20-token-send-redesign.spec.ts | 116 ++++++++++++++---- .../dapp-initiated-header.test.tsx.snap | 35 ++++++ .../header/dapp-initiated-header.test.tsx | 21 ++++ .../confirm/header/dapp-initiated-header.tsx | 39 ++++++ .../components/confirm/header/header.tsx | 2 + .../info/token-transfer/token-transfer.tsx | 18 +++ 9 files changed, 230 insertions(+), 21 deletions(-) create mode 100644 ui/pages/confirmations/components/confirm/header/__snapshots__/dapp-initiated-header.test.tsx.snap create mode 100644 ui/pages/confirmations/components/confirm/header/dapp-initiated-header.test.tsx create mode 100644 ui/pages/confirmations/components/confirm/header/dapp-initiated-header.tsx diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index e464e24be299..1e98d18c7b29 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -6283,6 +6283,9 @@ "transferFrom": { "message": "Transfer from" }, + "transferRequest": { + "message": "Transfer request" + }, "trillionAbbreviation": { "message": "T", "description": "Shortened form of 'trillion'" diff --git a/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts index 661feef33197..c7f618d3fc61 100644 --- a/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts +++ b/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts @@ -6,6 +6,8 @@ import Confirmation from './confirmation'; class TransactionConfirmation extends Confirmation { private walletInitiatedHeadingTitle: RawLocator; + private dappInitiatedHeadingTitle: RawLocator; + constructor(driver: Driver) { super(driver); @@ -15,11 +17,19 @@ class TransactionConfirmation extends Confirmation { css: 'h3', text: tEn('review') as string, }; + this.dappInitiatedHeadingTitle = { + css: 'h3', + text: tEn('transferRequest') as string, + }; } async check_walletInitiatedHeadingTitle() { await this.driver.waitForSelector(this.walletInitiatedHeadingTitle); } + + async check_dappInitiatedHeadingTitle() { + await this.driver.waitForSelector(this.dappInitiatedHeadingTitle); + } } export default TransactionConfirmation; diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index f729dffc429d..f450ce6d4946 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -17,6 +17,8 @@ class TestDapp { private erc20WatchAssetButton: RawLocator; + private erc20TokenTransferButton: RawLocator; + constructor(driver: Driver) { this.driver = driver; @@ -25,6 +27,7 @@ class TestDapp { this.erc721RevokeSetApprovalForAllButton = '#revokeButton'; this.erc1155RevokeSetApprovalForAllButton = '#revokeERC1155Button'; this.erc20WatchAssetButton = '#watchAssets'; + this.erc20TokenTransferButton = '#transferTokens'; } async open({ @@ -69,6 +72,10 @@ class TestDapp { public async clickERC20WatchAssetButton() { await this.driver.clickElement(this.erc20WatchAssetButton); } + + public async clickERC20TokenTransferButton() { + await this.driver.clickElement(this.erc20TokenTransferButton); + } } export default TestDapp; diff --git a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts index 16adf8a1fca5..a595d168045c 100644 --- a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts @@ -17,28 +17,68 @@ import { TestSuiteArguments } from './shared'; const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () { - it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( - this.test?.fullTitle(), - TransactionEnvelopeType.legacy, - async ({ driver, contractRegistry }: TestSuiteArguments) => { - await createTransactionAndAssertDetails(driver, contractRegistry); - }, - mocks, - SMART_CONTRACTS.HST, - ); + describe('Wallet initiated', async function () { + it('Sends a type 0 transaction (Legacy)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.legacy, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createWalletInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + mocks, + SMART_CONTRACTS.HST, + ); + }); + + it('Sends a type 2 transaction (EIP1559)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createWalletInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + mocks, + SMART_CONTRACTS.HST, + ); + }); }); - it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( - this.test?.fullTitle(), - TransactionEnvelopeType.feeMarket, - async ({ driver, contractRegistry }: TestSuiteArguments) => { - await createTransactionAndAssertDetails(driver, contractRegistry); - }, - mocks, - SMART_CONTRACTS.HST, - ); + describe('dApp initiated', async function () { + it('Sends a type 0 transaction (Legacy)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.legacy, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createDAppInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + mocks, + SMART_CONTRACTS.HST, + ); + }); + + it('Sends a type 2 transaction (EIP1559)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createDAppInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + mocks, + SMART_CONTRACTS.HST, + ); + }); }); }); @@ -46,7 +86,7 @@ async function mocks(server: Mockttp) { return [await mocked4BytesSetApprovalForAll(server)]; } -async function createTransactionAndAssertDetails( +async function createWalletInitiatedTransactionAndAssertDetails( driver: Driver, contractRegistry?: GanacheContractAddressRegistry, ) { @@ -90,3 +130,37 @@ async function createTransactionAndAssertDetails( await tokenTransferTransactionConfirmation.clickFooterConfirmButton(); } + +async function createDAppInitiatedTransactionAndAssertDetails( + driver: Driver, + contractRegistry?: GanacheContractAddressRegistry, +) { + await unlockWallet(driver); + + const contractAddress = await ( + contractRegistry as GanacheContractAddressRegistry + ).getContractAddress(SMART_CONTRACTS.HST); + + const testDapp = new TestDapp(driver); + + await testDapp.open({ contractAddress, url: DAPP_URL }); + + await testDapp.clickERC20WatchAssetButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const watchAssetConfirmation = new WatchAssetConfirmation(driver); + await watchAssetConfirmation.clickFooterConfirmButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await testDapp.clickERC20TokenTransferButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const tokenTransferTransactionConfirmation = + new TokenTransferTransactionConfirmation(driver); + await tokenTransferTransactionConfirmation.check_dappInitiatedHeadingTitle(); + await tokenTransferTransactionConfirmation.check_networkParagraph(); + await tokenTransferTransactionConfirmation.check_interactingWithParagraph(); + await tokenTransferTransactionConfirmation.check_networkFeeParagraph(); + + await tokenTransferTransactionConfirmation.clickFooterConfirmButton(); +} diff --git a/ui/pages/confirmations/components/confirm/header/__snapshots__/dapp-initiated-header.test.tsx.snap b/ui/pages/confirmations/components/confirm/header/__snapshots__/dapp-initiated-header.test.tsx.snap new file mode 100644 index 000000000000..26b0fed0b969 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/header/__snapshots__/dapp-initiated-header.test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should match snapshot 1`] = ` +
+
+

+ Transfer request +

+
+
+ +
+
+
+
+`; diff --git a/ui/pages/confirmations/components/confirm/header/dapp-initiated-header.test.tsx b/ui/pages/confirmations/components/confirm/header/dapp-initiated-header.test.tsx new file mode 100644 index 000000000000..4ae30d90936c --- /dev/null +++ b/ui/pages/confirmations/components/confirm/header/dapp-initiated-header.test.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { DefaultRootState } from 'react-redux'; +import { getMockTokenTransferConfirmState } from '../../../../../../test/data/confirmations/helper'; +import { renderWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers'; +import configureStore from '../../../../../store/store'; +import { DAppInitiatedHeader } from './dapp-initiated-header'; + +const render = ( + state: DefaultRootState = getMockTokenTransferConfirmState({}), +) => { + const store = configureStore(state); + return renderWithConfirmContextProvider(, store); +}; + +describe('', () => { + it('should match snapshot', () => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/header/dapp-initiated-header.tsx b/ui/pages/confirmations/components/confirm/header/dapp-initiated-header.tsx new file mode 100644 index 000000000000..3d4734659117 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/header/dapp-initiated-header.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Box, Text } from '../../../../../components/component-library'; +import { + AlignItems, + BackgroundColor, + Display, + FlexDirection, + JustifyContent, + TextColor, + TextVariant, +} from '../../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { AdvancedDetailsButton } from './advanced-details-button'; + +export const DAppInitiatedHeader = () => { + const t = useI18nContext(); + + return ( + + + {t('transferRequest')} + + + + + + ); +}; diff --git a/ui/pages/confirmations/components/confirm/header/header.tsx b/ui/pages/confirmations/components/confirm/header/header.tsx index 9c113effe6a5..dacc432612b8 100644 --- a/ui/pages/confirmations/components/confirm/header/header.tsx +++ b/ui/pages/confirmations/components/confirm/header/header.tsx @@ -22,6 +22,7 @@ import { useConfirmContext } from '../../../context/confirm'; import useConfirmationNetworkInfo from '../../../hooks/useConfirmationNetworkInfo'; import useConfirmationRecipientInfo from '../../../hooks/useConfirmationRecipientInfo'; import { Confirmation } from '../../../types/confirm'; +import { DAppInitiatedHeader } from './dapp-initiated-header'; import HeaderInfo from './header-info'; import { WalletInitiatedHeader } from './wallet-initiated-header'; @@ -39,6 +40,7 @@ const Header = () => { if (isWalletInitiated) { return ; } + return ; } return ( diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx index 0f0649659467..8800bde3d5c4 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx @@ -6,16 +6,34 @@ import { GasFeesSection } from '../shared/gas-fees-section/gas-fees-section'; import SendHeading from '../shared/send-heading/send-heading'; import { TokenDetailsSection } from './token-details-section'; import { TransactionFlowSection } from './transaction-flow-section'; +import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section'; +import { SimulationDetails } from '../../../simulation-details'; +import { useConfirmContext } from '../../../../context/confirm'; +import { TransactionMeta } from '@metamask/transaction-controller'; const TokenTransferInfo = () => { + const { currentConfirmation: transactionMeta } = + useConfirmContext(); + const showAdvancedDetails = useSelector( selectConfirmationAdvancedDetailsOpen, ); + const isWalletInitiated = transactionMeta.origin === 'metamask'; + return ( <> + {!isWalletInitiated && ( + + + + )} {showAdvancedDetails && }