From 34a5515e17fbc56c0001d54ed4f03a987d2c4482 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Fri, 18 Oct 2024 12:41:30 +0100 Subject: [PATCH] feat: NFT token transfer --- test/e2e/page-objects/pages/homepage.ts | 13 + .../page-objects/pages/nft-details-page.ts | 17 + .../pages/send/send-token-page.ts | 7 + test/e2e/page-objects/pages/test-dapp.ts | 36 ++ .../nft-token-send-redesign.spec.ts | 317 ++++++++++++++++++ .../components/confirm/header/header.tsx | 11 +- .../components/confirm/info/info.tsx | 3 + .../nft-token-transfer.stories.tsx | 42 +++ .../nft-token-transfer.test.tsx | 41 +++ .../nft-token-transfer/nft-token-transfer.tsx | 38 +++ .../nft-send-heading.test.tsx.snap | 21 ++ .../nft-send-heading.stories.tsx | 31 ++ .../nft-send-heading.test.tsx | 21 ++ .../nft-send-heading/nft-send-heading.tsx | 67 ++++ .../info/shared/send-heading/send-heading.tsx | 2 +- .../token-transfer.test.tsx.snap | 2 +- .../transaction-flow-section.tsx | 6 +- ui/pages/confirmations/utils/confirm.ts | 4 + 18 files changed, 674 insertions(+), 5 deletions(-) create mode 100644 test/e2e/page-objects/pages/nft-details-page.ts create mode 100644 test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts create mode 100644 ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.stories.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.test.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/__snapshots__/nft-send-heading.test.tsx.snap create mode 100644 ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.stories.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.test.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.tsx diff --git a/test/e2e/page-objects/pages/homepage.ts b/test/e2e/page-objects/pages/homepage.ts index 326ecc3188b7..3a2387b53752 100644 --- a/test/e2e/page-objects/pages/homepage.ts +++ b/test/e2e/page-objects/pages/homepage.ts @@ -11,6 +11,10 @@ class HomePage { private readonly activityTab = '[data-testid="account-overview__activity-tab"]'; + private readonly nftTab = '[data-testid="account-overview__nfts-tab"]'; + + private readonly nftIconOnActivityList = '[data-testid="nft-item"]'; + private readonly balance = '[data-testid="eth-overview__primary-currency"]'; private readonly completedTransactions = '[data-testid="activity-list-item"]'; @@ -60,6 +64,15 @@ class HomePage { await this.driver.clickElement(this.activityTab); } + async goToNFTList(): Promise { + console.log(`Open NFT tab on homepage`); + await this.driver.clickElement(this.nftTab); + } + + async clickNFTIconOnActivityList() { + await this.driver.clickElement(this.nftIconOnActivityList); + } + /** * This function checks if the specified number of confirmed transactions are displayed in the activity list on homepage. * It waits up to 10 seconds for the expected number of confirmed transactions to be visible. diff --git a/test/e2e/page-objects/pages/nft-details-page.ts b/test/e2e/page-objects/pages/nft-details-page.ts new file mode 100644 index 000000000000..b95477f9024d --- /dev/null +++ b/test/e2e/page-objects/pages/nft-details-page.ts @@ -0,0 +1,17 @@ +import { Driver } from '../../webdriver/driver'; + +class NFTDetailsPage { + private driver: Driver; + + private readonly nftSendButton = '[data-testid="nft-send-button"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async clickNFTSendButton() { + await this.driver.clickElement(this.nftSendButton); + } +} + +export default NFTDetailsPage; diff --git a/test/e2e/page-objects/pages/send/send-token-page.ts b/test/e2e/page-objects/pages/send/send-token-page.ts index 728afbfdd4df..60ffd86c6cdd 100644 --- a/test/e2e/page-objects/pages/send/send-token-page.ts +++ b/test/e2e/page-objects/pages/send/send-token-page.ts @@ -9,6 +9,8 @@ class SendTokenPage { private inputAmount: string; + private inputNFTAmount: string; + private scanButton: string; private continueButton: object; @@ -26,6 +28,7 @@ class SendTokenPage { constructor(driver: Driver) { this.driver = driver; this.inputAmount = '[data-testid="currency-input"]'; + this.inputNFTAmount = '[data-testid="nft-input"]'; this.inputRecipient = '[data-testid="ens-input"]'; this.scanButton = '[data-testid="ens-qr-scan-button"]'; this.ensResolvedName = @@ -79,6 +82,10 @@ class SendTokenPage { ); } + async fillNFTAmount(amount: string) { + await this.driver.pasteIntoField(this.inputNFTAmount, amount); + } + async goToNextScreen(): Promise { await this.driver.clickElement(this.continueButton); } diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index 4a02d80459e0..47370932fd3b 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -34,6 +34,18 @@ class TestDapp { tag: 'button', }; + private readonly erc721MintButton = '#mintButton'; + + private readonly erc721TransferFromButton = '#transferFromButton'; + + private readonly erc1155TokenIDInput = '#batchMintTokenIds'; + + private readonly erc1155TokenAmountInput = '#batchMintIdAmounts'; + + private readonly erc1155MintButton = '#batchMintButton'; + + private readonly erc1155WatchButton = '#watchAssetButton'; + private readonly erc1155RevokeSetApprovalForAllButton = '#revokeERC1155Button'; @@ -174,6 +186,30 @@ class TestDapp { }); } + async clickERC721MintButton() { + await this.driver.clickElement(this.erc721MintButton); + } + + async clickERC721TransferFromButton() { + await this.driver.clickElement(this.erc721TransferFromButton); + } + + async fillERC1155TokenID(tokenID: string) { + await this.driver.pasteIntoField(this.erc1155TokenIDInput, tokenID); + } + + async fillERC1155TokenAmount(amount: string) { + await this.driver.pasteIntoField(this.erc1155TokenAmountInput, amount); + } + + async clickERC1155MintButton() { + await this.driver.clickElement(this.erc1155MintButton); + } + + async clickERC1155WatchButton() { + await this.driver.clickElement(this.erc1155WatchButton); + } + async clickERC721SetApprovalForAllButton() { await this.driver.clickElement(this.erc721SetApprovalForAllButton); } diff --git a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts new file mode 100644 index 000000000000..9dfd77b87fab --- /dev/null +++ b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts @@ -0,0 +1,317 @@ +/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ +import { TransactionEnvelopeType } from '@metamask/transaction-controller'; +import { DAPP_URL } from '../../../constants'; +import { + unlockWallet, + veryLargeDelayMs, + WINDOW_TITLES, +} from '../../../helpers'; +import { Mockttp } from '../../../mock-e2e'; +import WatchAssetConfirmation from '../../../page-objects/pages/confirmations/legacy/watch-asset-confirmation'; +import TokenTransferTransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/token-transfer-confirmation'; +import TransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/transaction-confirmation'; +import HomePage from '../../../page-objects/pages/homepage'; +import NFTDetailsPage from '../../../page-objects/pages/nft-details-page'; +import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; +import TestDapp from '../../../page-objects/pages/test-dapp'; +import GanacheContractAddressRegistry from '../../../seeder/ganache-contract-address-registry'; +import { Driver } from '../../../webdriver/driver'; +import { withRedesignConfirmationFixtures } from '../helpers'; +import { TestSuiteArguments } from './shared'; + +const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); + +const TOKEN_RECIPIENT_ADDRESS = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; + +describe('Confirmation Redesign Token Send @no-mmi', function () { + describe('ERC721', function () { + 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 createERC721WalletInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + erc721Mocks, + SMART_CONTRACTS.NFTS, + ); + }); + + it('Sends a type 2 transaction (EIP1559)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createERC721WalletInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + erc721Mocks, + SMART_CONTRACTS.NFTS, + ); + }); + }); + + 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 createERC721DAppInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + erc721Mocks, + SMART_CONTRACTS.NFTS, + ); + }); + + it('Sends a type 2 transaction (EIP1559)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createERC721DAppInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + erc721Mocks, + SMART_CONTRACTS.NFTS, + ); + }); + }); + }); + + describe('ERC1155', function () { + 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 createERC1155WalletInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + erc1155Mocks, + SMART_CONTRACTS.ERC1155, + ); + }); + + it('Sends a type 2 transaction (EIP1559)', async function () { + await withRedesignConfirmationFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await createERC721WalletInitiatedTransactionAndAssertDetails( + driver, + contractRegistry, + ); + }, + erc1155Mocks, + SMART_CONTRACTS.ERC1155, + ); + }); + }); + }); +}); + +async function erc721Mocks(server: Mockttp) { + return [await mockedERC7214BytesNFTTokenSend(server)]; +} + +async function erc1155Mocks(server: Mockttp) { + return [await mockedERC11554BytesNFTTokenSend(server)]; +} + +export async function mockedERC7214BytesNFTTokenSend(mockServer: Mockttp) { + return await mockServer + .forGet('https://www.4byte.directory/api/v1/signatures/') + .withQuery({ hex_signature: '0x23b872dd' }) + .always() + .thenCallback(() => ({ + statusCode: 200, + json: { + count: 1, + next: null, + previous: null, + results: [ + { + bytes_signature: '#rÝ', + created_at: '2016-07-09T03:58:28.927638Z', + hex_signature: '0x23b872dd', + id: 147, + text_signature: 'transferFrom(address,address,uint256)', + }, + ], + }, + })); +} + +export async function mockedERC11554BytesNFTTokenSend(mockServer: Mockttp) { + return await mockServer + .forGet('https://www.4byte.directory/api/v1/signatures/') + .withQuery({ hex_signature: '0xf242432a' }) + .always() + .thenCallback(() => ({ + statusCode: 200, + json: { + count: 1, + next: null, + previous: null, + results: [ + { + bytes_signature: 'òBC*', + created_at: '2018-08-29T20:16:41.650553Z', + hex_signature: '0xf242432a', + id: 93843, + text_signature: + 'safeTransferFrom(address,address,uint256,uint256,bytes)', + }, + ], + }, + })); +} + +async function createERC721WalletInitiatedTransactionAndAssertDetails( + driver: Driver, + contractRegistry?: GanacheContractAddressRegistry, +) { + await unlockWallet(driver); + + const contractAddress = await ( + contractRegistry as GanacheContractAddressRegistry + ).getContractAddress(SMART_CONTRACTS.NFTS); + + const testDapp = new TestDapp(driver); + + await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + + await testDapp.clickERC721MintButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const mintConfirmation = new TransactionConfirmation(driver); + + await mintConfirmation.clickFooterConfirmButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + const homePage = new HomePage(driver); + await homePage.goToNFTList(); + await homePage.clickNFTIconOnActivityList(); + + const nftDetailsPage = new NFTDetailsPage(driver); + await nftDetailsPage.clickNFTSendButton(); + + const sendToPage = new SendTokenPage(driver); + await sendToPage.check_pageIsLoaded(); + await sendToPage.fillRecipient(TOKEN_RECIPIENT_ADDRESS); + await sendToPage.goToNextScreen(); + + const tokenTransferTransactionConfirmation = + new TokenTransferTransactionConfirmation(driver); + await tokenTransferTransactionConfirmation.check_walletInitiatedHeadingTitle(); + await tokenTransferTransactionConfirmation.check_networkParagraph(); + await tokenTransferTransactionConfirmation.check_interactingWithParagraph(); + await tokenTransferTransactionConfirmation.check_networkFeeParagraph(); + + await tokenTransferTransactionConfirmation.clickFooterConfirmButton(); +} + +async function createERC721DAppInitiatedTransactionAndAssertDetails( + driver: Driver, + contractRegistry?: GanacheContractAddressRegistry, +) { + await unlockWallet(driver); + + const contractAddress = await ( + contractRegistry as GanacheContractAddressRegistry + ).getContractAddress(SMART_CONTRACTS.NFTS); + + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + await testDapp.clickERC721MintButton(); + + await driver.delay(veryLargeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const mintConfirmation = new TransactionConfirmation(driver); + await mintConfirmation.clickFooterConfirmButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await testDapp.clickERC721TransferFromButton(); + + await driver.delay(veryLargeDelayMs); + 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(); +} + +async function createERC1155WalletInitiatedTransactionAndAssertDetails( + driver: Driver, + contractRegistry?: GanacheContractAddressRegistry, +) { + await unlockWallet(driver); + + const contractAddress = await ( + contractRegistry as GanacheContractAddressRegistry + ).getContractAddress(SMART_CONTRACTS.ERC1155); + + const testDapp = new TestDapp(driver); + + await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + await testDapp.fillERC1155TokenID('1'); + await testDapp.fillERC1155TokenAmount('1'); + await testDapp.clickERC1155MintButton(); + + await driver.delay(veryLargeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const mintConfirmation = new TransactionConfirmation(driver); + await mintConfirmation.clickFooterConfirmButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await testDapp.clickERC1155WatchButton(); + + await driver.delay(veryLargeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const watchAssetConfirmation = new WatchAssetConfirmation(driver); + await watchAssetConfirmation.clickFooterConfirmButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + const homePage = new HomePage(driver); + await homePage.goToNFTList(); + await homePage.clickNFTIconOnActivityList(); + + const nftDetailsPage = new NFTDetailsPage(driver); + await nftDetailsPage.clickNFTSendButton(); + + const sendToPage = new SendTokenPage(driver); + await sendToPage.check_pageIsLoaded(); + await sendToPage.fillRecipient(TOKEN_RECIPIENT_ADDRESS); + await sendToPage.fillNFTAmount('1'); + await sendToPage.goToNextScreen(); + + const tokenTransferTransactionConfirmation = + new TokenTransferTransactionConfirmation(driver); + await tokenTransferTransactionConfirmation.check_walletInitiatedHeadingTitle(); + await tokenTransferTransactionConfirmation.check_networkParagraph(); + await tokenTransferTransactionConfirmation.check_interactingWithParagraph(); + await tokenTransferTransactionConfirmation.check_networkFeeParagraph(); + + await tokenTransferTransactionConfirmation.clickFooterConfirmButton(); +} diff --git a/ui/pages/confirmations/components/confirm/header/header.tsx b/ui/pages/confirmations/components/confirm/header/header.tsx index dacc432612b8..abda93fba510 100644 --- a/ui/pages/confirmations/components/confirm/header/header.tsx +++ b/ui/pages/confirmations/components/confirm/header/header.tsx @@ -33,7 +33,16 @@ const Header = () => { const { currentConfirmation } = useConfirmContext(); - if (currentConfirmation?.type === TransactionType.tokenMethodTransfer) { + const CONFIRMATIONS_WITH_NEW_HEADER = [ + TransactionType.tokenMethodTransfer, + TransactionType.tokenMethodTransferFrom, + TransactionType.tokenMethodSafeTransferFrom, + ]; + + if ( + currentConfirmation?.type && + CONFIRMATIONS_WITH_NEW_HEADER.includes(currentConfirmation.type) + ) { const isWalletInitiated = (currentConfirmation as TransactionMeta).origin === 'metamask'; diff --git a/ui/pages/confirmations/components/confirm/info/info.tsx b/ui/pages/confirmations/components/confirm/info/info.tsx index 3e87f4f7908c..67bac40d7e61 100644 --- a/ui/pages/confirmations/components/confirm/info/info.tsx +++ b/ui/pages/confirmations/components/confirm/info/info.tsx @@ -9,6 +9,7 @@ import SetApprovalForAllInfo from './set-approval-for-all-info/set-approval-for- import TokenTransferInfo from './token-transfer/token-transfer'; import TypedSignV1Info from './typed-sign-v1/typed-sign-v1'; import TypedSignInfo from './typed-sign/typed-sign'; +import NFTTokenTransferInfo from './nft-token-transfer/nft-token-transfer'; const Info = () => { const { currentConfirmation } = useConfirmContext(); @@ -31,6 +32,8 @@ const Info = () => { [TransactionType.tokenMethodSetApprovalForAll]: () => SetApprovalForAllInfo, [TransactionType.tokenMethodTransfer]: () => TokenTransferInfo, + [TransactionType.tokenMethodTransferFrom]: () => NFTTokenTransferInfo, + [TransactionType.tokenMethodSafeTransferFrom]: () => NFTTokenTransferInfo, }), [currentConfirmation], ); diff --git a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.stories.tsx b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.stories.tsx new file mode 100644 index 000000000000..084698b2e2fa --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.stories.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { getMockTokenTransferConfirmState } from '../../../../../../../test/data/confirmations/helper'; +import { Box } from '../../../../../../components/component-library'; +import { + AlignItems, + Display, + FlexDirection, + JustifyContent, +} from '../../../../../../helpers/constants/design-system'; +import configureStore from '../../../../../../store/store'; +import { ConfirmContextProvider } from '../../../../context/confirm'; +import NFTTokenTransferInfo from './nft-token-transfer'; + +const store = configureStore(getMockTokenTransferConfirmState({})); + +const Story = { + title: 'Components/App/Confirm/info/NFTTransferInfo', + component: NFTTokenTransferInfo, + decorators: [ + (story: () => any) => ( + + + + {story()} + + + + ), + ], +}; + +export default Story; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.test.tsx b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.test.tsx new file mode 100644 index 000000000000..77fd4485e3dc --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.test.tsx @@ -0,0 +1,41 @@ +import { screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import { getMockTokenTransferConfirmState } from '../../../../../../../test/data/confirmations/helper'; +import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; +import { tEn } from '../../../../../../../test/lib/i18n-helpers'; +import NFTTokenTransferInfo from './nft-token-transfer'; + +jest.mock( + '../../../../../../components/app/alert-system/contexts/alertMetricsContext', + () => ({ + useAlertMetrics: jest.fn(() => ({ + trackAlertMetrics: jest.fn(), + })), + }), +); + +jest.mock('../../../../../../store/actions', () => ({ + ...jest.requireActual('../../../../../../store/actions'), + getGasFeeTimeEstimate: jest.fn().mockResolvedValue({ + lowerTimeBound: 0, + upperTimeBound: 60000, + }), +})); + +describe('NFTTokenTransferInfo', () => { + it('renders correctly', async () => { + const state = getMockTokenTransferConfirmState({}); + const mockStore = configureMockStore([])(state); + const { container } = renderWithConfirmContextProvider( + , + mockStore, + ); + + await waitFor(() => { + expect(screen.getByText(tEn('networkFee') as string)).toBeInTheDocument(); + }); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx new file mode 100644 index 000000000000..a2e6a73b4353 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx @@ -0,0 +1,38 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import React from 'react'; +import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section'; +import { useConfirmContext } from '../../../../context/confirm'; +import { SimulationDetails } from '../../../simulation-details'; +import { AdvancedDetails } from '../shared/advanced-details/advanced-details'; +import { GasFeesSection } from '../shared/gas-fees-section/gas-fees-section'; +import NFTSendHeading from '../shared/nft-send-heading/nft-send-heading'; +import { TokenDetailsSection } from '../token-transfer/token-details-section'; +import { TransactionFlowSection } from '../token-transfer/transaction-flow-section'; + +const NFTTokenTransferInfo = () => { + const { currentConfirmation: transactionMeta } = + useConfirmContext(); + + const isWalletInitiated = transactionMeta.origin === 'metamask'; + + return ( + <> + + + {!isWalletInitiated && ( + + + + )} + + + + + ); +}; + +export default NFTTokenTransferInfo; diff --git a/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/__snapshots__/nft-send-heading.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/__snapshots__/nft-send-heading.test.tsx.snap new file mode 100644 index 000000000000..2e98c4e5ce13 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/__snapshots__/nft-send-heading.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders component 1`] = ` +
+
+
+ ? +
+

+

+

+
+`; diff --git a/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.stories.tsx b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.stories.tsx new file mode 100644 index 000000000000..d2c02deb1c7e --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.stories.tsx @@ -0,0 +1,31 @@ +import { Meta } from '@storybook/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { getMockTokenTransferConfirmState } from '../../../../../../../../test/data/confirmations/helper'; +import configureStore from '../../../../../../../store/store'; +import { ConfirmContextProvider } from '../../../../../context/confirm'; +import NFTSendHeading from './nft-send-heading'; + +const store = configureStore(getMockTokenTransferConfirmState({})); + +const Story = { + title: 'Components/App/Confirm/info/NFTSendHeading', + component: NFTSendHeading, + decorators: [ + (story: () => Meta) => ( + + {story()} + + ), + ], +}; + +export default Story; + +export const DefaultStory = () => ( + + + +); + +DefaultStory.storyName = 'Default'; diff --git a/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.test.tsx b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.test.tsx new file mode 100644 index 000000000000..0d2b1fd898f8 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.test.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { getMockTokenTransferConfirmState } from '../../../../../../../../test/data/confirmations/helper'; +import { renderWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; +import NFTSendHeading from './nft-send-heading'; + +describe('', () => { + const middleware = [thunk]; + const state = getMockTokenTransferConfirmState({}); + const mockStore = configureMockStore(middleware)(state); + + it('renders component', () => { + const { container } = renderWithConfirmContextProvider( + , + mockStore, + ); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.tsx b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.tsx new file mode 100644 index 000000000000..006613e206c9 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/shared/nft-send-heading/nft-send-heading.tsx @@ -0,0 +1,67 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import React from 'react'; +import { + AvatarToken, + AvatarTokenSize, + Box, + Text, +} from '../../../../../../../components/component-library'; +import { + AlignItems, + Display, + FlexDirection, + JustifyContent, + TextColor, + TextVariant, +} from '../../../../../../../helpers/constants/design-system'; +import { useConfirmContext } from '../../../../../context/confirm'; +import { useAssetDetails } from '../../../../../hooks/useAssetDetails'; + +const NFTSendHeading = () => { + const { currentConfirmation: transactionMeta } = + useConfirmContext(); + + const tokenAddress = transactionMeta.txParams.to; + const userAddress = transactionMeta.txParams.from; + const { data } = transactionMeta.txParams; + + const { assetName, tokenImage, tokenId } = useAssetDetails( + tokenAddress, + userAddress, + data, + ); + + const TokenImage = ; + + const TokenName = ( + + {assetName} + + ); + + const TokenID = ( + + {tokenId} + + ); + + return ( + + {TokenImage} + {TokenName} + {TokenID} + + ); +}; + +export default NFTSendHeading; diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx index 2806c33936c0..730e81d59ff6 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx @@ -57,7 +57,7 @@ const SendHeading = () => { variant={TextVariant.headingLg} color={TextColor.inherit} marginTop={3} - >{`${decodedTransferValue || ''} ${ + >{`${decodedTransferValue} ${ selectedToken?.symbol || t('unknown') }`} {fiatDisplayValue && ( diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap index e1d19241252b..f7a672912907 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap @@ -13,7 +13,7 @@ exports[`TokenTransferInfo renders correctly 1`] = `

- Unknown + 0 Unknown

{ const { value, pending } = useDecodedTransactionData(); - const recipientAddress = value?.data[0].params.find( + const addresses = value?.data[0].params.filter( (param) => param.type === 'address', - )?.value; + ); + // sometimes there's more than one address, in which case we want the last one + const recipientAddress = addresses?.[addresses.length - 1].value; if (pending) { return ; diff --git a/ui/pages/confirmations/utils/confirm.ts b/ui/pages/confirmations/utils/confirm.ts index a33c1dae735c..ef2c1791bc9b 100644 --- a/ui/pages/confirmations/utils/confirm.ts +++ b/ui/pages/confirmations/utils/confirm.ts @@ -23,10 +23,14 @@ export const REDESIGN_USER_TRANSACTION_TYPES = [ TransactionType.tokenMethodIncreaseAllowance, TransactionType.tokenMethodSetApprovalForAll, TransactionType.tokenMethodTransfer, + TransactionType.tokenMethodTransferFrom, + TransactionType.tokenMethodSafeTransferFrom, ]; export const REDESIGN_DEV_TRANSACTION_TYPES = [ ...REDESIGN_USER_TRANSACTION_TYPES, + TransactionType.tokenMethodTransferFrom, + TransactionType.tokenMethodSafeTransferFrom, ]; const SIGNATURE_APPROVAL_TYPES = [