From 06f78a137dd3dacd28424cc92f09af38f1270c94 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Thu, 9 Nov 2023 17:50:49 +0100 Subject: [PATCH 01/17] fix: pending state for social login (#2785) --- src/components/common/SocialSigner/index.tsx | 16 +++++++++++++++- .../common/SocialSigner/styles.module.css | 10 ++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/components/common/SocialSigner/index.tsx b/src/components/common/SocialSigner/index.tsx index 503327fd49..ac30d1e5ca 100644 --- a/src/components/common/SocialSigner/index.tsx +++ b/src/components/common/SocialSigner/index.tsx @@ -1,4 +1,4 @@ -import { Box, Button, SvgIcon, Tooltip, Typography } from '@mui/material' +import { Box, Button, LinearProgress, SvgIcon, Tooltip, Typography } from '@mui/material' import { useCallback, useContext, useMemo, useState } from 'react' import { PasswordRecovery } from '@/components/common/SocialSigner/PasswordRecovery' import GoogleLogo from '@/public/images/welcome/logo-google.svg' @@ -118,6 +118,13 @@ export const SocialSigner = ({ disabled={isDisabled} fullWidth > + + Continue with Google diff --git a/src/components/common/SocialSigner/styles.module.css b/src/components/common/SocialSigner/styles.module.css index 92c29890de..2b81df624a 100644 --- a/src/components/common/SocialSigner/styles.module.css +++ b/src/components/common/SocialSigner/styles.module.css @@ -10,6 +10,16 @@ align-items: flex-start; } +.loginProgress { + margin-bottom: -16px; + top: 0; + width: 100%; + height: 2px; + position: absolute; + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} + .passwordWrapper { padding: var(--space-4) var(--space-4) var(--space-2) var(--space-4); display: flex; From 33e98269d93a3e4eb0bab2d2b261aa7410519470 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Thu, 9 Nov 2023 18:17:01 +0100 Subject: [PATCH 02/17] chore: add event label to google login button (#2786) * chore: add event label to google login button * fix: additional requested analytics changes --- src/components/common/SocialSigner/index.tsx | 4 +++- src/services/analytics/events/mpcWallet.ts | 7 ++++++- src/services/mpc/SocialWalletService.ts | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/common/SocialSigner/index.tsx b/src/components/common/SocialSigner/index.tsx index ac30d1e5ca..c7f1138219 100644 --- a/src/components/common/SocialSigner/index.tsx +++ b/src/components/common/SocialSigner/index.tsx @@ -63,6 +63,8 @@ export const SocialSigner = ({ const userInfo = socialWalletService?.getUserInfo() const isDisabled = loginPending || !isMPCLoginEnabled + const isWelcomePage = !!onLogin + const recoverPassword = useCallback( async (password: string, storeDeviceFactor: boolean) => { if (!socialWalletService) return @@ -143,7 +145,7 @@ export const SocialSigner = ({ ) : ( - + + + + )} + + + + ) +} + export default AssetsTable From a9ce0c1589a2a68ebb0eae8159a69f59a04e9a76 Mon Sep 17 00:00:00 2001 From: Michael <30682308+mike10ca@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:26:20 +0100 Subject: [PATCH 04/17] Tests: Update testids (#2784) * Update test IDs --- cypress/e2e/safe-apps/apps_list.cy.js | 16 ++++---- .../e2e/safe-apps/browser_permissions.cy.js | 4 +- .../e2e/safe-apps/drain_account.spec.cy.js | 18 ++++----- cypress/e2e/safe-apps/info_modal.cy.js | 8 ++-- .../e2e/safe-apps/permissions_settings.cy.js | 18 ++++----- cypress/e2e/safe-apps/preview_drawer.cy.js | 4 +- cypress/e2e/safe-apps/safe_permissions.cy.js | 4 +- cypress/e2e/safe-apps/tx-builder.spec.cy.js | 38 +++++++++---------- cypress/e2e/safe-apps/tx_modal.cy.js | 2 +- cypress/e2e/smoke/add_owner.cy.js | 20 +++++----- cypress/e2e/smoke/address_book.cy.js | 12 +++--- cypress/e2e/smoke/assets.cy.js | 36 +++++++++--------- cypress/e2e/smoke/balances.cy.js | 16 ++++---- cypress/e2e/smoke/balances_pagination.cy.js | 4 +- cypress/e2e/smoke/batch_tx.cy.js | 12 +++--- cypress/e2e/smoke/beamer.cy.js | 2 +- cypress/e2e/smoke/create_safe_simple.cy.js | 27 +++++++------ cypress/e2e/smoke/create_tx.cy.js | 6 +-- cypress/e2e/smoke/dashboard.cy.js | 8 ++-- cypress/e2e/smoke/import_export_data.cy.js | 10 ++--- cypress/e2e/smoke/landing.cy.js | 2 +- cypress/e2e/smoke/load_safe.cy.js | 10 ++--- cypress/e2e/smoke/nfts.cy.js | 12 +++--- cypress/e2e/smoke/pending_actions.cy.js | 2 +- cypress/e2e/smoke/remove_owner.cy.js | 12 +++--- cypress/e2e/smoke/replace_owner.cy.js | 16 ++++---- cypress/e2e/smoke/tx_history.cy.js | 4 +- 27 files changed, 161 insertions(+), 162 deletions(-) diff --git a/cypress/e2e/safe-apps/apps_list.cy.js b/cypress/e2e/safe-apps/apps_list.cy.js index 9265117bcd..0ef421f1e7 100644 --- a/cypress/e2e/safe-apps/apps_list.cy.js +++ b/cypress/e2e/safe-apps/apps_list.cy.js @@ -5,14 +5,14 @@ import * as safeapps from '../pages/safeapps.pages' const myCustomAppTitle = 'Cypress Test App' const myCustomAppDescrAdded = 'Cypress Test App Description' -describe('Safe Apps tests', () => { +describe('Safe Apps list tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.SEPOLIA_TEST_SAFE_4 + constants.appsUrl, { failOnStatusCode: false }) main.acceptCookies() }) - it('Verify app list can be filtered by app name [C56130]', () => { + it('Verify app list can be filtered by app name', () => { // Wait for /safe-apps response cy.intercept('GET', constants.appsEndpoint).then(() => { safeapps.typeAppName(constants.appNames.walletConnect) @@ -20,29 +20,29 @@ describe('Safe Apps tests', () => { }) }) - it('Verify app list can be filtered by app description [C56131]', () => { + it('Verify app list can be filtered by app description', () => { safeapps.typeAppName(constants.appNames.customContract) safeapps.verifyLinkName(safeapps.linkNames.logo) }) - it('Verify error message is displayed when no app found [C56132]', () => { + it('Verify error message is displayed when no app found', () => { safeapps.typeAppName(constants.appNames.noResults) safeapps.verifyNoAppsTextPresent() }) - it('Verify apps can be pinned [C56133]', () => { + it('Verify apps can be pinned', () => { safeapps.clearSearchAppInput() safeapps.pinApp(safeapps.transactionBuilderStr) safeapps.verifyPinnedAppCount(1) }) - it('Verify apps can be unpinned [C56134]', () => { + it('Verify apps can be unpinned', () => { safeapps.pinApp(safeapps.transactionBuilderStr) safeapps.pinApp(safeapps.transactionBuilderStr, false) safeapps.verifyPinnedAppCount(0) }) - it('Verify there is an error when the app manifest is invalid [C56135]', () => { + it('Verify there is an error when the app manifest is invalid', () => { cy.intercept('GET', constants.invalidAppUrl, { name: constants.testAppData.name, }) @@ -52,7 +52,7 @@ describe('Safe Apps tests', () => { safeapps.verifyAppNotSupportedMsg() }) - it('Verify an app can be added to the list within the custom apps section [C56136]', () => { + it('Verify an app can be added to the list within the custom apps section', () => { cy.intercept('GET', constants.validAppUrlJson, { name: constants.testAppData.name, description: constants.testAppData.descr, diff --git a/cypress/e2e/safe-apps/browser_permissions.cy.js b/cypress/e2e/safe-apps/browser_permissions.cy.js index 916604d9a6..59cbceffed 100644 --- a/cypress/e2e/safe-apps/browser_permissions.cy.js +++ b/cypress/e2e/safe-apps/browser_permissions.cy.js @@ -18,13 +18,13 @@ describe('Browser permissions tests', () => { main.acceptCookies() }) - it('Verify a permissions slide to the user is displayed [C56137]', () => { + it('Verify a permissions slide to the user is displayed', () => { safeapps.clickOnContinueBtn() safeapps.verifyCameraCheckBoxExists() safeapps.verifyMicrofoneCheckBoxExists() }) - it('Verify the selection can be changed, accepted and stored [C56138]', () => { + it('Verify the selection can be changed, accepted and stored', () => { safeapps.verifyMicrofoneCheckBoxExists().click() safeapps.clickOnContinueBtn() safeapps.verifyWarningDefaultAppMsgIsDisplayed() diff --git a/cypress/e2e/safe-apps/drain_account.spec.cy.js b/cypress/e2e/safe-apps/drain_account.spec.cy.js index 1536de692f..773559d748 100644 --- a/cypress/e2e/safe-apps/drain_account.spec.cy.js +++ b/cypress/e2e/safe-apps/drain_account.spec.cy.js @@ -3,7 +3,7 @@ import * as constants from '../../support/constants' import * as main from '../pages/main.page' import * as safeapps from '../pages/safeapps.pages' -describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => { +describe('Drain Account tests', { defaultCommandTimeout: 12000 }, () => { const appUrl = constants.drainAccount_url const iframeSelector = `iframe[id="iframe-${appUrl}"]` const visitUrl = `/apps/open?safe=${constants.GOERLI_SAFE_APPS_SAFE}&appUrl=${encodeURIComponent(appUrl)}` @@ -19,7 +19,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => safeapps.clickOnContinueBtn() }) - it('Verify drain can be created [C56627]', () => { + it('Verify drain can be created', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.recipientStr).type(constants.SAFE_APP_ADDRESS_2) getBody().findAllByText(safeapps.transferEverythingStr).click() @@ -28,7 +28,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => cy.findByRole('button', { name: safeapps.testNativeTransfer2 }) }) - it('Verify partial drain can be created [C56628]', () => { + it('Verify partial drain can be created', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.selectAllRowsChbxStr).click() getBody().findAllByLabelText(safeapps.selectRowChbxStr).eq(1).click() @@ -40,7 +40,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => cy.findByRole('button', { name: safeapps.testNativeTransfer1 }) }) - it('Verify a drain can be created when a ENS is specified [C56629]', () => { + it('Verify a drain can be created when a ENS is specified', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.recipientStr).type('goerli-test-safe.eth').wait(2000) getBody().findAllByText(safeapps.transferEverythingStr).click() @@ -50,7 +50,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => }) // TODO: Adjust safe - owner - it.skip('Verify when cancelling a drain, previous data is preserved [C56630]', () => { + it.skip('Verify when cancelling a drain, previous data is preserved', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.recipientStr).type(constants.SAFE_APP_ADDRESS_2) getBody().findAllByText(safeapps.transferEverythingStr).click() @@ -61,14 +61,14 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => }) }) - it('Verify a drain cannot be created with no recipient selected [C56631]', () => { + it('Verify a drain cannot be created with no recipient selected', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findAllByText(safeapps.transferEverythingStr).click() getBody().findByText(safeapps.validRecipientAddressStr) }) }) - it('Verify a drain cannot be created with invalid recipient selected [C56632]', () => { + it('Verify a drain cannot be created with invalid recipient selected', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.recipientStr).type(constants.SAFE_APP_ADDRESS_2.substring(1)) getBody().findAllByText(safeapps.transferEverythingStr).click() @@ -76,7 +76,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => }) }) - it('Verify a drain cannot be created when no assets are selected [C56633]', () => { + it('Verify a drain cannot be created when no assets are selected', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.selectAllRowsChbxStr).click() getBody().findByLabelText(safeapps.recipientStr).type(constants.SAFE_APP_ADDRESS_2) @@ -84,7 +84,7 @@ describe('Drain Account Safe App tests', { defaultCommandTimeout: 12000 }, () => }) }) - it('should not allow to perform a drain when no assets and recipient are selected', () => { + it('Verify a drain cannot be created when no assets and recipient are selected', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.selectAllRowsChbxStr).click() getBody().findAllByText(safeapps.noTokensSelectedStr).should('be.visible') diff --git a/cypress/e2e/safe-apps/info_modal.cy.js b/cypress/e2e/safe-apps/info_modal.cy.js index 650fe1b041..7aeab75e2e 100644 --- a/cypress/e2e/safe-apps/info_modal.cy.js +++ b/cypress/e2e/safe-apps/info_modal.cy.js @@ -2,27 +2,27 @@ import * as constants from '../../support/constants' import * as main from '../pages/main.page' import * as safeapps from '../pages/safeapps.pages' -describe('Safe Apps info modal tests', () => { +describe('Info modal tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(constants.SEPOLIA_TEST_SAFE_5 + constants.appsUrl, { failOnStatusCode: false }) main.acceptCookies() }) - it('Verify the disclaimer is displayed when a Safe App is opened [C56139]', () => { + it('Verify the disclaimer is displayed when a Safe App is opened', () => { safeapps.clickOnApp(safeapps.transactionBuilderStr) safeapps.clickOnOpenSafeAppBtn() }) // Skip tests due to changed logic // TODO: Discuss furthers - it.skip('Verify the permissions slide is shown if the app require permissions [C56140]', () => { + it.skip('Verify the permissions slide is shown if the app require permissions', () => { safeapps.clickOnContinueBtn() cy.wait(500) // wait for the animation to finish safeapps.verifyCameraCheckBoxExists() }) - it.skip('Verify the permissions and consents decision are stored when accepted [C56141]', () => { + it.skip('Verify the permissions and consents decision are stored when accepted', () => { safeapps.storeAndVerifyPermissions() }) }) diff --git a/cypress/e2e/safe-apps/permissions_settings.cy.js b/cypress/e2e/safe-apps/permissions_settings.cy.js index ca88523a74..41484f9638 100644 --- a/cypress/e2e/safe-apps/permissions_settings.cy.js +++ b/cypress/e2e/safe-apps/permissions_settings.cy.js @@ -6,7 +6,7 @@ let $dapps = [] const app1 = 'https://app1.com' const app3 = 'https://app3.com' -describe('Safe Apps permissions settings tests', () => { +describe('Permissions settings tests', () => { before(() => { cy.clearLocalStorage() cy.on('window:before:load', (window) => { @@ -49,18 +49,18 @@ describe('Safe Apps permissions settings tests', () => { main.acceptCookies() }) - it('Verify for each stored app the permissions configuration is shown [C56142]', () => { + it('Verify for each stored app the permissions configuration is shown', () => { cy.findAllByRole('heading', { level: 5 }).should('have.length', 4) }) - describe('Permissions for each app', () => { + describe('Permissions for each Safe app', () => { before(() => { cy.get(safeapps.gridItem).then((items) => { $dapps = items }) }) - it('Verify that app1 has camera, full screen and geo permissions [C56143]', () => { + it('Verify that app1 has camera, full screen and geo permissions', () => { const app1Data = [ 'app1', safeapps.permissionCheckboxNames.camera, @@ -74,7 +74,7 @@ describe('Safe Apps permissions settings tests', () => { main.verifyCheckboxeState(safeapps.permissionCheckboxes.fullscreen, 0, constants.checkboxStates.checked) }) - it('Verify that app2 has address book and microphone permissions [C56144]', () => { + it('Verify that app2 has address book and microphone permissions', () => { const app2Data = [ 'app2', safeapps.permissionCheckboxNames.addressbook, @@ -86,21 +86,21 @@ describe('Safe Apps permissions settings tests', () => { main.verifyCheckboxeState(safeapps.permissionCheckboxes.addressbook, 0, constants.checkboxStates.checked) }) - it('Verify that app3 has camera permissions [C56145]', () => { + it('Verify that app3 has camera permissions', () => { const app3Data = ['app3', safeapps.permissionCheckboxNames.camera] main.checkTextsExistWithinElement($dapps[2], app3Data) main.verifyCheckboxeState(safeapps.permissionCheckboxes.camera, 1, constants.checkboxStates.unchecked) }) - it('Verify that app4 has address book permissions [C56146]', () => { + it('Verify that app4 has address book permissions', () => { const app4Data = ['app4', safeapps.permissionCheckboxNames.addressbook] main.checkTextsExistWithinElement($dapps[3], app4Data) main.verifyCheckboxeState(safeapps.permissionCheckboxes.addressbook, 1, constants.checkboxStates.checked) }) - it('Verify Allow all or Clear all the checkboxes at once is permitted [C56147]', () => { + it('Verify Allow all or Clear all the checkboxes at once is permitted', () => { safeapps.uncheckAllPermissions($dapps[1]) main.verifyCheckboxeState(safeapps.permissionCheckboxes.addressbook, 0, constants.checkboxStates.unchecked) main.verifyCheckboxeState(safeapps.permissionCheckboxes.microphone, 0, constants.checkboxStates.unchecked) @@ -110,7 +110,7 @@ describe('Safe Apps permissions settings tests', () => { main.verifyCheckboxeState(safeapps.permissionCheckboxes.microphone, 0, constants.checkboxStates.checked) }) - it('Verify it is permitted to remove apps and reflect it in the localStorage [C56148]', () => { + it('Verify it is permitted to remove apps and reflect it in the localStorage', () => { cy.wrap($dapps[0]).find('svg').last().click() cy.wrap($dapps[2]) .find('svg') diff --git a/cypress/e2e/safe-apps/preview_drawer.cy.js b/cypress/e2e/safe-apps/preview_drawer.cy.js index 985a1bcad6..d9d3b35cf4 100644 --- a/cypress/e2e/safe-apps/preview_drawer.cy.js +++ b/cypress/e2e/safe-apps/preview_drawer.cy.js @@ -2,14 +2,14 @@ import * as constants from '../../support/constants' import * as main from '../pages/main.page' import * as safeapps from '../pages/safeapps.pages' -describe('Safe Apps info modal tests', () => { +describe('Preview drawer tests', () => { beforeEach(() => { cy.clearLocalStorage() cy.visit(`/${constants.SEPOLIA_TEST_SAFE_5}/apps`, { failOnStatusCode: false }) main.acceptCookies() }) - it('Verify the preview drawer is displayed when opening a Safe App from the app list [C56149]', () => { + it('Verify the preview drawer is displayed when opening a Safe App from the app list', () => { safeapps.clickOnApp(safeapps.transactionBuilderStr) cy.findByRole('presentation').within(() => { diff --git a/cypress/e2e/safe-apps/safe_permissions.cy.js b/cypress/e2e/safe-apps/safe_permissions.cy.js index 3b2b6fa6f8..0b64e41ad9 100644 --- a/cypress/e2e/safe-apps/safe_permissions.cy.js +++ b/cypress/e2e/safe-apps/safe_permissions.cy.js @@ -15,7 +15,7 @@ describe('Safe permissions system tests', () => { }) }) - it('Verify that requesting permissions with wallet_requestPermissions shows the permissions prompt and return the permissions on accept [C56150]', () => { + it('Verify that requesting permissions with wallet_requestPermissions shows the permissions prompt and return the permissions on accept', () => { cy.visitSafeApp(constants.testAppUrl + constants.requestPermissionsUrl) main.acceptCookies() safeapps.clickOnContinueBtn() @@ -38,7 +38,7 @@ describe('Safe permissions system tests', () => { }) }) - it('Verify that trying to get the current permissions with wallet_getPermissions returns the current permissions [C56151]', () => { + it('Verify that trying to get the current permissions with wallet_getPermissions returns the current permissions', () => { cy.on('window:before:load', (window) => { window.localStorage.setItem( constants.SAFE_PERMISSIONS_KEY, diff --git a/cypress/e2e/safe-apps/tx-builder.spec.cy.js b/cypress/e2e/safe-apps/tx-builder.spec.cy.js index e2399d03e4..da1bd43e40 100644 --- a/cypress/e2e/safe-apps/tx-builder.spec.cy.js +++ b/cypress/e2e/safe-apps/tx-builder.spec.cy.js @@ -3,7 +3,7 @@ import * as constants from '../../support/constants' import * as main from '../pages/main.page' import * as safeapps from '../pages/safeapps.pages' -describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { +describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { const appUrl = constants.TX_Builder_url const iframeSelector = `iframe[id="iframe-${appUrl}"]` const visitUrl = `/apps/open?safe=${constants.GOERLI_SAFE_APPS_SAFE}&appUrl=${encodeURIComponent(appUrl)}` @@ -14,7 +14,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { safeapps.clickOnContinueBtn() }) - it('Verify a simple batch can be created [C56609]', () => { + it('Verify a simple batch can be created', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS) getBody().find(safeapps.contractMethodIndex).parent().click() @@ -32,7 +32,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.findAllByText(constants.SAFE_APP_ADDRESS_2_SHORT).should('have.length', 1) }) - it('Verify a complex batch can be created [C56610]', () => { + it('Verify a complex batch can be created', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS) getBody().find(safeapps.contractMethodIndex).parent().click() @@ -54,7 +54,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.findAllByText('True').should('have.length', 3) }) - it('Verify a batch can be created using ENS name [C56611]', () => { + it('Verify a batch can be created using ENS name', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.ENS_TEST_GOERLI) getBody().findByRole('button', { name: safeapps.useImplementationABI }).click() @@ -72,7 +72,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.findByText(safeapps.thresholdStr2).should('exist') }) - it('Verify a batch can be created from an ABI [C56612]', () => { + it('Verify a batch can be created from an ABI', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterABIStr).type(safeapps.abi) getBody().findByLabelText(safeapps.toAddressStr).type(constants.SAFE_APP_ADDRESS_2) @@ -85,7 +85,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.findByText(constants.SAFE_APP_ADDRESS_2).should('be.visible') }) - it('Verify a batch with custom data can be created [C56613]', () => { + it('Verify a batch with custom data can be created', () => { cy.enter(iframeSelector).then((getBody) => { getBody().find('.MuiSwitch-root').click() getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS_3) @@ -99,7 +99,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.findByText(constants.SAFE_APP_ADDRESS_3).should('be.visible') }) - it('Verify a batch can be cancelled [C56614]', () => { + it('Verify a batch can be cancelled', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS) getBody().find(safeapps.contractMethodIndex).parent().click() @@ -114,7 +114,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify cancel operation can be reverted [C56615]', () => { + it('Verify cancel operation can be reverted', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS) getBody().find(safeapps.contractMethodIndex).parent().click() @@ -129,7 +129,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify it is allowed to go back without removing data and add more transactions to the batch [C56616]', () => { + it('Verify it is allowed to go back without removing data and add more transactions to the batch', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS) getBody().find(safeapps.contractMethodIndex).parent().click() @@ -150,14 +150,14 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.get('p').contains('2').should('be.visible') }) - it('Verify a batch cannot be created with invalid address [C56617]', () => { + it('Verify a batch cannot be created with invalid address', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS_3) getBody().findAllByText(safeapps.addressNotValidStr).should('have.css', 'color', 'rgb(244, 67, 54)') }) }) - it('Verify a batch cannot be created without asset amount [C56618]', () => { + it('Verify a batch cannot be created without asset amount', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS_3) getBody().findByText(safeapps.addTransactionStr).click() @@ -165,7 +165,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify a batch cannot be created without method data [C56619]', () => { + it('Verify a batch cannot be created without method data', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS_2) getBody().findByText(safeapps.addTransactionStr).click() @@ -173,7 +173,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify a batch can be uploaded, saved, downloaded and removed [C56620]', () => { + it('Verify a batch can be uploaded, saved, downloaded and removed', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findAllByText('choose a file').attachFile('test-working-batch.json', { subjectType: 'drag-n-drop' }) getBody().findAllByText('uploaded').wait(300) @@ -190,7 +190,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { cy.readFile('cypress/downloads/E2E test.json').should('exist') }) - it('Verify there is notification if uploaded batch is from a different chain [C56621]', () => { + it('Verify there is notification if uploaded batch is from a different chain', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findAllByText('choose a file').attachFile('test-mainnet-batch.json', { subjectType: 'drag-n-drop' }) getBody().findAllByText(safeapps.warningStr).should('be.visible') @@ -198,7 +198,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify there is error message when a modified batch is uploaded [C56622]', () => { + it('Verify there is error message when a modified batch is uploaded', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findAllByText('choose a file').attachFile('test-modified-batch.json', { subjectType: 'drag-n-drop' }) getBody().findAllByText(safeapps.changedPropertiesStr) @@ -206,7 +206,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify an invalid batch cannot be uploaded [C56623]', () => { + it('Verify an invalid batch cannot be uploaded', () => { cy.enter(iframeSelector).then((getBody) => { getBody() .findAllByText('choose a file') @@ -216,7 +216,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify an empty batch cannot be uploaded [C56624]', () => { + it('Verify an empty batch cannot be uploaded', () => { cy.enter(iframeSelector).then((getBody) => { getBody() .findAllByText('choose a file') @@ -226,7 +226,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify a valid batch as successful can be simulated [C56625]', () => { + it('Verify a valid batch as successful can be simulated', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS_3) getBody().findByLabelText(safeapps.gorValue).type('0') @@ -238,7 +238,7 @@ describe('Tx-builder Safe App tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify an invalid batch as failed can be simulated [C56626]', () => { + it('Verify an invalid batch as failed can be simulated', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.TEST_SAFE_2) getBody().findByText(safeapps.keepProxiABIStr).click() diff --git a/cypress/e2e/safe-apps/tx_modal.cy.js b/cypress/e2e/safe-apps/tx_modal.cy.js index b2961038a1..38e46c2d03 100644 --- a/cypress/e2e/safe-apps/tx_modal.cy.js +++ b/cypress/e2e/safe-apps/tx_modal.cy.js @@ -19,7 +19,7 @@ describe('Transaction modal tests', () => { }) it( - 'Verify that the transaction popup is displayed when sending a transaction from an app [C56152]', + 'Verify that the transaction popup is displayed when sending a transaction from an app', { defaultCommandTimeout: 12000 }, () => { cy.visitSafeApp(`${constants.testAppUrl}/dummy`) diff --git a/cypress/e2e/smoke/add_owner.cy.js b/cypress/e2e/smoke/add_owner.cy.js index e19dbca96b..2397881b65 100644 --- a/cypress/e2e/smoke/add_owner.cy.js +++ b/cypress/e2e/smoke/add_owner.cy.js @@ -11,35 +11,35 @@ describe('Add Owners tests', () => { cy.contains(owner.safeAccountNonceStr, { timeout: 10000 }) }) - it('Verify the presence of "Add Owner" button [C56017]', () => { + it('Verify the presence of "Add Owner" button', () => { owner.verifyAddOwnerBtnIsEnabled() }) - it('Verify “Add new owner” button tooltip displays correct message for Non-Owner [C56018]', () => { + it('Verify “Add new owner” button tooltip displays correct message for Non-Owner', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_2) owner.verifyAddOwnerBtnIsDisabled() }) - it('Verify Tooltip displays correct message for disconnected user [C56019]', () => { + it('Verify Tooltip displays correct message for disconnected user', () => { owner.waitForConnectionStatus() owner.clickOnWalletExpandMoreIcon() owner.clickOnDisconnectBtn() owner.verifyAddOwnerBtnIsDisabled() }) - it('Verify the Add New Owner Form can be opened [C56020]', () => { + it('Verify the Add New Owner Form can be opened', () => { owner.waitForConnectionStatus() owner.openAddOwnerWindow() }) - it('Verify error message displayed if character limit is exceeded in Name input [C56022]', () => { + it('Verify error message displayed if character limit is exceeded in Name input', () => { owner.waitForConnectionStatus() owner.openAddOwnerWindow() owner.typeOwnerName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) }) - it('Verify that the "Name" field is auto-filled with the relevant name from Address Book [C56023]', () => { + it('Verify that the "Name" field is auto-filled with the relevant name from Address Book', () => { cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_1) addressBook.clickOnCreateEntryBtn() addressBook.typeInName(constants.addresBookContacts.user1.name) @@ -54,7 +54,7 @@ describe('Add Owners tests', () => { owner.verifyNewOwnerName(constants.addresBookContacts.user1.name) }) - it('Verify that Name field not mandatory [C56024]', () => { + it('Verify that Name field not mandatory', () => { owner.waitForConnectionStatus() owner.openAddOwnerWindow() owner.typeOwnerAddress(constants.SEPOLIA_OWNER_2) @@ -62,7 +62,7 @@ describe('Add Owners tests', () => { owner.verifyConfirmTransactionWindowDisplayed() }) - it('Verify relevant error messages are displayed in Address input [C56025]', () => { + it('Verify relevant error messages are displayed in Address input', () => { owner.waitForConnectionStatus() owner.openAddOwnerWindow() owner.typeOwnerAddress(main.generateRandomString(10)) @@ -81,14 +81,14 @@ describe('Add Owners tests', () => { owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.alreadyAdded) }) - it('Verify default threshold value. Verify correct threshold calculation [C56028]', () => { + it('Verify default threshold value. Verify correct threshold calculation', () => { owner.waitForConnectionStatus() owner.openAddOwnerWindow() owner.typeOwnerAddress(constants.DEFAULT_OWNER_ADDRESS) owner.verifyThreshold(1, 2) }) - it('Verify valid Address validation [C56027]', () => { + it('Verify valid Address validation', () => { owner.waitForConnectionStatus() owner.openAddOwnerWindow() owner.typeOwnerAddress(constants.SEPOLIA_OWNER_2) diff --git a/cypress/e2e/smoke/address_book.cy.js b/cypress/e2e/smoke/address_book.cy.js index d14ca5e2b4..b0af66eecd 100644 --- a/cypress/e2e/smoke/address_book.cy.js +++ b/cypress/e2e/smoke/address_book.cy.js @@ -15,12 +15,12 @@ describe('Address book tests', () => { main.acceptCookies() }) - it('Verify entry can be added [C56061]', () => { + it('Verify entry can be added', () => { addressBook.clickOnCreateEntryBtn() addressBook.addEntry(NAME, constants.RECIPIENT_ADDRESS) }) - it('Verify entered entry in Name input can be saved [C56063]', () => { + it('Verify entered entry in Name input can be saved', () => { addressBook.clickOnCreateEntryBtn() addressBook.addEntry(NAME, constants.RECIPIENT_ADDRESS) addressBook.clickOnEditEntryBtn() @@ -29,7 +29,7 @@ describe('Address book tests', () => { addressBook.verifyNameWasChanged(NAME, EDITED_NAME) }) - it('Verify entry can be deleted [C56062]', () => { + it('Verify entry can be deleted', () => { addressBook.clickOnCreateEntryBtn() addressBook.addEntry(NAME, constants.RECIPIENT_ADDRESS) // Click the delete button in the first entry @@ -38,14 +38,14 @@ describe('Address book tests', () => { addressBook.verifyEditedNameNotExists(EDITED_NAME) }) - it('Verify csv file can be imported (Goerli) [C56064]', () => { + it('Verify csv file can be imported (Goerli)', () => { addressBook.clickOnImportFileBtn() addressBook.importFile() addressBook.verifyImportModalIsClosed() addressBook.verifyDataImported(constants.SEPOLIA_CSV_ENTRY.name, constants.SEPOLIA_CSV_ENTRY.address) }) - it.skip('Verify Gnosis Chain imported address can be found [C56066]', () => { + it.skip('Verify Gnosis Chain imported address can be found', () => { // Go to a Safe on Gnosis Chain cy.get('header') .contains(/^G(ö|oe)rli$/) @@ -63,7 +63,7 @@ describe('Address book tests', () => { cy.contains(constants.GNO_CSV_ENTRY.address).should('exist') }) - it('Verify the address book file can be downloaded [C56065]', () => { + it('Verify the address book file can be downloaded', () => { addressBook.clickOnImportFileBtn() addressBook.importFile() // Download the export file diff --git a/cypress/e2e/smoke/assets.cy.js b/cypress/e2e/smoke/assets.cy.js index b063ef0eaf..ebdf8cad3a 100644 --- a/cypress/e2e/smoke/assets.cy.js +++ b/cypress/e2e/smoke/assets.cy.js @@ -16,15 +16,15 @@ describe('Assets tests', () => { main.acceptCookies() }) - it('Verify that the token tab is selected by default and the table is visible [C56039]', () => { + it('Verify that the token tab is selected by default and the table is visible', () => { balances.verifyTokensTabIsSelected('true') }) - it('Verify that the native token is visible [C56040]', () => { + it('Verify that the native token is visible', () => { balances.verifyTokenIsPresent(constants.tokenNames.sepoliaEther) }) - it('Verify that non-native tokens are present and have balance [C56041]', () => { + it('Verify that non-native tokens are present and have balance', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.verifyBalance(balances.currencyDaiCap, TOKEN_AMOUNT_COLUMN, balances.currencyDaiAlttext) balances.verifyTokenBalanceFormat( @@ -81,7 +81,7 @@ describe('Assets tests', () => { ) }) - it('Verify that every token except the native token has a "go to blockexplorer link" [C56042]', () => { + it('Verify that every token except the native token has a "go to blockexplorer link"', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) // Specifying true for Sepolia. Will delete the flag once completely migrate to Sepolia balances.verifyAssetNameHasExplorerLink(balances.currencyUSDC, ASSET_NAME_COLUMN, true) @@ -93,7 +93,7 @@ describe('Assets tests', () => { balances.verifyAssetExplorerLinkNotAvailable(constants.tokenNames.sepoliaEther, ASSET_NAME_COLUMN) }) - it('Verify the default Fiat currency and the effects after changing it [C56043]', () => { + it('Verify the default Fiat currency and the effects after changing it', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.verifyFirstRowDoesNotContainCurrency(balances.currencyEUR, FIAT_AMOUNT_COLUMN) balances.verifyFirstRowContainsCurrency(balances.currencyUSD, FIAT_AMOUNT_COLUMN) @@ -103,12 +103,12 @@ describe('Assets tests', () => { balances.verifyFirstRowContainsCurrency(balances.currencyEUR, FIAT_AMOUNT_COLUMN) }) - it('Verify that a tool tip is shown pointing to "Token list" dropdown [C56044]', () => { + it('Verify that a tool tip is shown pointing to "Token list" dropdown', () => { //Spam warning message is removed in beforeEach hook cy.reload() }) - it('Verify that Token list dropdown down options show/hide spam tokens [C56045]', () => { + it('Verify that Token list dropdown down options show/hide spam tokens', () => { let spamTokens = [ balances.currencyAave, balances.currencyTestTokenA, @@ -124,20 +124,20 @@ describe('Assets tests', () => { main.verifyValuesExist(balances.tokenListTable, spamTokens) }) - it('Verify that "Hide token" button is present and opens the "Hide tokens menu" [C56046]', () => { + it('Verify that "Hide token" button is present and opens the "Hide tokens menu"', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.openHideTokenMenu() balances.verifyEachRowHasCheckbox() }) - it('Verify that checking the checkboxes increases the token selected counter [C56047]', () => { + it('Verify that checking the checkboxes increases the token selected counter', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.openHideTokenMenu() balances.clickOnTokenCheckbox(balances.currencyLink) balances.checkTokenCounter(1) }) - it('Verify that selecting tokens and saving hides them from the table [C56048]', () => { + it('Verify that selecting tokens and saving hides them from the table', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.openHideTokenMenu() balances.clickOnTokenCheckbox(balances.currencyLink) @@ -145,7 +145,7 @@ describe('Assets tests', () => { main.verifyValuesDoNotExist(balances.tokenListTable, [balances.currencyLink]) }) - it('Verify that Cancel closes the menu and does not change the table status [C56049]', () => { + it('Verify that Cancel closes the menu and does not change the table status', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.openHideTokenMenu() balances.clickOnTokenCheckbox(balances.currencyLink) @@ -159,7 +159,7 @@ describe('Assets tests', () => { main.verifyValuesDoNotExist(balances.tokenListTable, [balances.currencyLink, balances.currencyAave]) }) - it('Verify that Deselect All unchecks all tokens from the list [C56050]', () => { + it('Verify that Deselect All unchecks all tokens from the list', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.openHideTokenMenu() balances.clickOnTokenCheckbox(balances.currencyLink) @@ -168,7 +168,7 @@ describe('Assets tests', () => { balances.verifyEachRowHasCheckbox(constants.checkboxStates.unchecked) }) - it('Verify the Hidden tokens counter works for spam tokens [C56051]', () => { + it('Verify the Hidden tokens counter works for spam tokens', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.openHideTokenMenu() balances.clickOnTokenCheckbox(balances.currencyLink) @@ -176,19 +176,19 @@ describe('Assets tests', () => { balances.checkHiddenTokenBtnCounter(1) }) - it('Verify the Hidden tokens counter works for native tokens [C56056]', () => { + it('Verify the Hidden tokens counter works for native tokens', () => { balances.openHideTokenMenu() balances.clickOnTokenCheckbox(constants.tokenNames.sepoliaEther) balances.saveHiddenTokenSelection() balances.checkHiddenTokenBtnCounter(1) }) - it('Verify you can hide tokens from the eye icon in the table rows [C56053]', () => { + it('Verify you can hide tokens from the eye icon in the table rows', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.hideAsset(balances.currencyLink) }) - it('Verify the sorting of "Assets" and "Balance" in the table [C56052]', () => { + it('Verify the sorting of "Assets" and "Balance" in the table', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.verifyTableRows(7) balances.clickOnTokenNameSortBtn() @@ -201,12 +201,12 @@ describe('Assets tests', () => { balances.verifyTokenBalanceOrder('descending') }) - it('Verify that clicking the button with an owner opens the Send funds form [C56055]', () => { + it('Verify that clicking the button with an owner opens the Send funds form', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) balances.clickOnSendBtn(0) }) - it('Verify that the Send button shows when hovering a row [C56054]', () => { + it('Verify that the Send button shows when hovering a row', () => { owner.clickOnWalletExpandMoreIcon() owner.clickOnDisconnectBtn() balances.selectTokenList(balances.tokenListOptions.allTokens) diff --git a/cypress/e2e/smoke/balances.cy.js b/cypress/e2e/smoke/balances.cy.js index 7d5a0bb7b9..0b1da92302 100644 --- a/cypress/e2e/smoke/balances.cy.js +++ b/cypress/e2e/smoke/balances.cy.js @@ -21,25 +21,25 @@ describe('Balance tests', () => { cy.get(balances.balanceSingleRow).should('have.length', ASSETS_LENGTH) }) - it('Verify that token is present: Dai [C56074]', () => { + it('Verify that token is present: Dai', () => { balances.verityTokenAltImageIsVisible(balances.currencyDaiCap, balances.currencyDaiAlttext) balances.verifyAssetNameHasExplorerLink(balances.currencyDaiCap, ASSET_NAME_COLUMN) balances.verifyBalance(balances.currencyDaiCap, TOKEN_AMOUNT_COLUMN, balances.currencyDaiAlttext) }) - it('Verify that token is present: AAVE [C56075]', () => { + it('Verify that token is present: AAVE', () => { balances.verityTokenAltImageIsVisible(balances.currencyAave, balances.currencyAaveAlttext) balances.verifyAssetNameHasExplorerLink(balances.currencyAave, ASSET_NAME_COLUMN) balances.verifyBalance(balances.currencyAave, TOKEN_AMOUNT_COLUMN, balances.currencyAaveAlttext) }) - it('Verify that token is present: LINK [C56076]', () => { + it('Verify that token is present: LINK', () => { balances.verityTokenAltImageIsVisible(balances.currencyLink, balances.currencyLinkAlttext) balances.verifyAssetNameHasExplorerLink(balances.currencyLink, ASSET_NAME_COLUMN) balances.verifyBalance(balances.currencyLink, TOKEN_AMOUNT_COLUMN, balances.currencyLinkAlttext) }) - it('Verify Token and Fiat balances formatted as per specification [C56077]', () => { + it('Verify Token and Fiat balances formatted as per specification', () => { balances.verifyTokenBalanceFormat( balances.currencyDaiCap, balances.currentcyDaiFormat, @@ -97,23 +97,23 @@ describe('Balance tests', () => { ) }) - it('Verify USD is default currency [C56078]', () => { + it('Verify USD is default currency', () => { balances.verifyFirstRowDoesNotContainCurrency(balances.currencyEUR, FIAT_AMOUNT_COLUMN) balances.verifyFirstRowContainsCurrency(balances.currencyUSD, FIAT_AMOUNT_COLUMN) }) - it('Verify currency can be changed to EUR [C56079]', () => { + it('Verify currency can be changed to EUR', () => { balances.clickOnCurrencyDropdown() balances.selectCurrency(balances.currencyEUR) balances.verifyFirstRowDoesNotContainCurrency(balances.currencyUSD, FIAT_AMOUNT_COLUMN) balances.verifyFirstRowContainsCurrency(balances.currencyEUR, FIAT_AMOUNT_COLUMN) }) - it('Verify a token can be hidden [C56080]', () => { + it('Verify a token can be hidden', () => { balances.hideAsset(balances.currencyDaiCap) }) - it('Verify a token can be unhidden [C56081]', () => { + it('Verify a token can be unhidden', () => { balances.hideAsset(balances.currencyDaiCap) balances.openHideTokenMenu() balances.clickOnTokenCheckbox(balances.currencyDaiCap) diff --git a/cypress/e2e/smoke/balances_pagination.cy.js b/cypress/e2e/smoke/balances_pagination.cy.js index 27703f9c2b..767780c633 100644 --- a/cypress/e2e/smoke/balances_pagination.cy.js +++ b/cypress/e2e/smoke/balances_pagination.cy.js @@ -4,7 +4,7 @@ import * as main from '../../e2e/pages/main.page' const ASSETS_LENGTH = 8 -describe('Balance tests', () => { +describe('Balance pagination tests', () => { before(() => { cy.clearLocalStorage() // Open the Safe used for testing @@ -14,7 +14,7 @@ describe('Balance tests', () => { balances.selectTokenList(balances.tokenListOptions.allTokens) }) - it('Verify a user can change rows per page and navigate to next and previous page [C56073]', () => { + it('Verify a user can change rows per page and navigate to next and previous page', () => { balances.verifyInitialTableState() balances.changeTo10RowsPerPage() balances.verifyTableHas10Rows() diff --git a/cypress/e2e/smoke/batch_tx.cy.js b/cypress/e2e/smoke/batch_tx.cy.js index da2ad88b33..c3e93d08fc 100644 --- a/cypress/e2e/smoke/batch_tx.cy.js +++ b/cypress/e2e/smoke/batch_tx.cy.js @@ -15,17 +15,17 @@ describe('Batch transaction tests', () => { main.acceptCookies() }) - it('Verify empty batch list can be opened [C56082]', () => { + it('Verify empty batch list can be opened', () => { batch.openBatchtransactionsModal() batch.openNewTransactionModal() }) - it('Verify the Add batch button is present in a transaction form [C56084]', () => { + it('Verify the Add batch button is present in a transaction form', () => { //The "true" is to validate that the add to batch button is not visible if "Yes, execute" is selected batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) }) - it('Verify a transaction can be added to the batch [C56085]', () => { + it('Verify a transaction can be added to the batch', () => { batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) cy.contains(batch.transactionAddedToBatchStr).should('be.visible') //The batch button in the header shows the transaction count @@ -34,7 +34,7 @@ describe('Batch transaction tests', () => { batch.verifyAmountTransactionsInBatch(1) }) - it('Verify a second transaction can be added to the batch [C56086]', () => { + it('Verify a second transaction can be added to the batch', () => { batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) cy.wait(1000) batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) @@ -43,7 +43,7 @@ describe('Batch transaction tests', () => { batch.verifyAmountTransactionsInBatch(2) }) - it('Verify the batch can be confirmed and related transactions exist in the form [C56088]', () => { + it('Verify the batch can be confirmed and related transactions exist in the form', () => { batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) cy.wait(1000) batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) @@ -58,7 +58,7 @@ describe('Batch transaction tests', () => { cy.get('@TransactionList').find('li').eq(1).find('span').eq(0).contains(funds_first_tx) }) - it('Verify a transaction can be removed from the batch [C56089]', () => { + it('Verify a transaction can be removed from the batch', () => { batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) cy.wait(1000) batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx) diff --git a/cypress/e2e/smoke/beamer.cy.js b/cypress/e2e/smoke/beamer.cy.js index ab0ee152b9..4280e7f15a 100644 --- a/cypress/e2e/smoke/beamer.cy.js +++ b/cypress/e2e/smoke/beamer.cy.js @@ -9,7 +9,7 @@ describe('Beamer tests', () => { main.acceptCookies() }) - it.skip('Verify "Updates" cookie acceptance is required before displaying Beamer [C56090]', () => { + it.skip('Verify "Updates" cookie acceptance is required before displaying Beamer', () => { addressbook.clickOnWhatsNewBtn() addressbook.acceptBeamerCookies() addressbook.verifyBeamerIsChecked() diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js index 7f5a8690c3..1b924cd743 100644 --- a/cypress/e2e/smoke/create_safe_simple.cy.js +++ b/cypress/e2e/smoke/create_safe_simple.cy.js @@ -9,8 +9,7 @@ describe('Safe creation tests', () => { cy.clearLocalStorage() main.acceptCookies() }) - - it('Verify a Wallet can be connected [C56101]', () => { + it('Verify a Wallet can be connected', () => { createwallet.clickOnCreateNewSafeBtn() owner.clickOnWalletExpandMoreIcon() owner.clickOnDisconnectBtn() @@ -18,7 +17,7 @@ describe('Safe creation tests', () => { createwallet.connectWallet() }) - it('Verify Next button is disabled until switching to network is done [C56102]', () => { + it('Verify Next button is disabled until switching to network is done', () => { owner.waitForConnectionStatus() createwallet.selectNetwork(constants.networks.ethereum) createwallet.clickOnCreateNewSafeBtn() @@ -28,13 +27,13 @@ describe('Safe creation tests', () => { createwallet.verifyNextBtnIsEnabled() }) - it('Verify that a new Wallet has default name related to the selected network [C56099]', () => { + it('Verify that a new Wallet has default name related to the selected network', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() createwallet.verifyDefaultWalletName(createwallet.defaltSepoliaPlaceholder) }) - it('Verify error message is displayed if wallet name input exceeds 50 characters [C56098]', () => { + it('Verify error message is displayed if wallet name input exceeds 50 characters', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() createwallet.typeWalletName(main.generateRandomString(51)) @@ -42,35 +41,35 @@ describe('Safe creation tests', () => { createwallet.clearWalletName() }) - it('Verify there is no error message is displayed if wallet name input contains less than 50 characters [C56100]', () => { + it('Verify there is no error message is displayed if wallet name input contains less than 50 characters', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() createwallet.typeWalletName(main.generateRandomString(50)) owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) }) - it('Verify current connected account is shown as default owner [C56091]', () => { + it('Verify current connected account is shown as default owner', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() owner.verifyExistingOwnerAddress(0, constants.DEFAULT_OWNER_ADDRESS) }) - it('Verify error message is displayed if owner name input exceeds 50 characters [C56092]', () => { + it('Verify error message is displayed if owner name input exceeds 50 characters', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.typeExistingOwnerName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) }) - it('Verify there is no error message is displayed if owner name input contains less than 50 characters [C56093]', () => { + it('Verify there is no error message is displayed if owner name input contains less than 50 characters', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.typeExistingOwnerName(main.generateRandomString(50)) owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) }) - it('Verify Add and Remove Owner Row works as expected [C56094]', () => { + it('Verify Add and Remove Owner Row works as expected', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() @@ -84,7 +83,7 @@ describe('Safe creation tests', () => { owner.verifyNumberOfOwners(2) }) - it('Verify Threshold Setup [C56096]', () => { + it('Verify Threshold Setup', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() @@ -102,7 +101,7 @@ describe('Safe creation tests', () => { createwallet.updateThreshold(1) }) - it('Verify data persistence [C56103]', () => { + it('Verify data persistence', () => { const ownerName = 'David' owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() @@ -135,14 +134,14 @@ describe('Safe creation tests', () => { createwallet.verifyEstimatedFeeInSummaryStep() }) - it('Verify tip is displayed on right side for threshold 1/1 [C56097]', () => { + it('Verify tip is displayed on right side for threshold 1/1', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() createwallet.verifyPolicy1_1() }) - it('Verify address input validation rules [C56095]', () => { + it('Verify address input validation rules', () => { owner.waitForConnectionStatus() createwallet.clickOnCreateNewSafeBtn() owner.clickOnNextBtn() diff --git a/cypress/e2e/smoke/create_tx.cy.js b/cypress/e2e/smoke/create_tx.cy.js index a96db26acc..74718d6980 100644 --- a/cypress/e2e/smoke/create_tx.cy.js +++ b/cypress/e2e/smoke/create_tx.cy.js @@ -12,7 +12,7 @@ describe('Create transactions tests', () => { main.acceptCookies() }) - it('Verify a new send token transaction can be created [C56104]', () => { + it('Verify a new send token transaction can be created', () => { createtx.clickOnNewtransactionBtn() createtx.clickOnSendTokensBtn() createtx.typeRecipientAddress(constants.EOA) @@ -23,7 +23,7 @@ describe('Create transactions tests', () => { createtx.clickOnNextBtn() }) - it('Verify a transaction can be reviewed, edited and submitted [C56105]', () => { + it('Verify a transaction can be reviewed, edited and submitted', () => { createtx.verifySubmitBtnIsEnabled() cy.wait(1000) createtx.verifyNativeTokenTransfer() @@ -36,7 +36,7 @@ describe('Create transactions tests', () => { createtx.clickOnSignTransactionBtn() }) - it('Verify that clicking on notification shows the transaction in queue [C56106]', () => { + it('Verify that clicking on notification shows the transaction in queue', () => { createtx.waitForProposeRequest() createtx.clickViewTransaction() createtx.verifySingleTxPage() diff --git a/cypress/e2e/smoke/dashboard.cy.js b/cypress/e2e/smoke/dashboard.cy.js index c69c918df1..d3712a5b07 100644 --- a/cypress/e2e/smoke/dashboard.cy.js +++ b/cypress/e2e/smoke/dashboard.cy.js @@ -11,19 +11,19 @@ describe('Dashboard tests', () => { dashboard.verifyConnectTransactStrIsVisible() }) - it('Verify the overview widget is displayed [C56107]', () => { + it('Verify the overview widget is displayed', () => { dashboard.verifyOverviewWidgetData() }) - it('Verify the transaction queue widget is displayed [C56108]', () => { + it('Verify the transaction queue widget is displayed', () => { dashboard.verifyTxQueueWidget() }) - it('Verify the featured Safe Apps are displayed [C56109]', () => { + it('Verify the featured Safe Apps are displayed', () => { dashboard.verifyFeaturedAppsSection() }) - it('Verify the Safe Apps Section is displayed [C56110]', () => { + it('Verify the Safe Apps Section is displayed', () => { dashboard.verifySafeAppsSection() }) }) diff --git a/cypress/e2e/smoke/import_export_data.cy.js b/cypress/e2e/smoke/import_export_data.cy.js index 2bcc125943..2d99d5d9b7 100644 --- a/cypress/e2e/smoke/import_export_data.cy.js +++ b/cypress/e2e/smoke/import_export_data.cy.js @@ -10,7 +10,7 @@ describe('Import Export Data tests', () => { main.acceptCookies() }) - it('Verify Safe can be accessed after test file upload [C56111]', () => { + it('Verify Safe can be accessed after test file upload', () => { const filePath = '../fixtures/data_import.json' const safe = constants.SEPOLIA_CSV_ENTRY.name @@ -23,20 +23,20 @@ describe('Import Export Data tests', () => { file.clickOnClosePushNotificationsBanner() }) - it('Verify address book imported data [C56112]', () => { + it('Verify address book imported data', () => { main.acceptCookies() file.clickOnAddressBookBtn() file.verifyImportedAddressBookData() }) - it('Verify pinned apps [C56113]', () => { + it('Verify pinned apps', () => { const appNames = ['Transaction Builder'] file.clickOnAppsBtn() file.verifyAppsAreVisible(appNames) }) - it('Verify imported data in settings [C56114]', () => { + it('Verify imported data in settings', () => { const unchecked = [file.prependChainPrefixStr, file.copyAddressStr] const checked = [file.darkModeStr] file.clickOnSettingsBtn() @@ -45,7 +45,7 @@ describe('Import Export Data tests', () => { file.verifyCheckboxes(checked, true) }) - it('Verifies data for export in Data tab [C56115]', () => { + it('Verifies data for export in Data tab', () => { file.clickOnShowMoreTabsBtn() file.verifDataTabBtnIsVisible() file.clickOnDataTab() diff --git a/cypress/e2e/smoke/landing.cy.js b/cypress/e2e/smoke/landing.cy.js index 5482541966..206732446f 100644 --- a/cypress/e2e/smoke/landing.cy.js +++ b/cypress/e2e/smoke/landing.cy.js @@ -1,6 +1,6 @@ import * as constants from '../../support/constants' describe('Landing page tests', () => { - it('Verify a user will be redirected to welcome page [C56116]', () => { + it('Verify a user will be redirected to welcome page', () => { cy.clearLocalStorage() cy.visit('/') cy.url().should('include', constants.welcomeUrl) diff --git a/cypress/e2e/smoke/load_safe.cy.js b/cypress/e2e/smoke/load_safe.cy.js index 8aaa66bede..edce4b7f8b 100644 --- a/cypress/e2e/smoke/load_safe.cy.js +++ b/cypress/e2e/smoke/load_safe.cy.js @@ -26,7 +26,7 @@ describe('Load Safe tests', () => { cy.wait(2000) }) - it('Verify a network can be selected in the Safe [C56117]', () => { + it('Verify a network can be selected in the Safe', () => { safe.clickNetworkSelector(constants.networks.sepolia) safe.selectPolygon() cy.wait(2000) @@ -34,7 +34,7 @@ describe('Load Safe tests', () => { safe.selectSepolia() }) - it('Verify only valid Safe name can be accepted [C56118]', () => { + it('Verify only valid Safe name can be accepted', () => { // alias the address input label cy.get('input[name="address"]').parent().prev('label').as('addressLabel') @@ -62,14 +62,14 @@ describe('Load Safe tests', () => { safe.clickOnNextBtn() }) - it('Verify custom name in the first owner an be set [C56120]', () => { + it('Verify custom name in the first owner an be set', () => { safe.inputNameAndAddress(testSafeName, constants.SEPOLIA_TEST_SAFE_1) safe.clickOnNextBtn() createwallet.typeOwnerName(testOwnerName, 0) safe.clickOnNextBtn() }) - it('Verify Safe and owner names are displayed in the Review step [C56121]', () => { + it('Verify Safe and owner names are displayed in the Review step', () => { safe.inputNameAndAddress(testSafeName, constants.SEPOLIA_TEST_SAFE_1) safe.clickOnNextBtn() createwallet.typeOwnerName(testOwnerName, 0) @@ -78,7 +78,7 @@ describe('Load Safe tests', () => { safe.clickOnAddBtn() }) - it('Verify the custom Safe name is successfully loaded [C56122]', () => { + it('Verify the custom Safe name is successfully loaded', () => { safe.inputNameAndAddress(testSafeName, constants.SEPOLIA_TEST_SAFE_2) safe.clickOnNextBtn() createwallet.typeOwnerName(testOwnerName, 0) diff --git a/cypress/e2e/smoke/nfts.cy.js b/cypress/e2e/smoke/nfts.cy.js index b6015e592a..1ecc5c1c20 100644 --- a/cypress/e2e/smoke/nfts.cy.js +++ b/cypress/e2e/smoke/nfts.cy.js @@ -14,27 +14,27 @@ describe('NFTs tests', () => { nfts.clickOnNftsTab() }) - it('Verify that NFTs exist in the table [C56123]', () => { + it('Verify that NFTs exist in the table', () => { nfts.verifyNFTNumber(20) }) - it('Verify NFT row contains data [C56124]', () => { + it('Verify NFT row contains data', () => { nfts.verifyDataInTable(nftsName, nftsAddress, nftsTokenID) }) - it('Verify NFT preview window can be opened [C56125]', () => { - nfts.openNFT(1) + it('Verify NFT preview window can be opened', () => { + nfts.openNFT(0) nfts.verifyNameInNFTModal(nftsTokenID) nfts.verifySelectedNetwrokSepolia() nfts.closeNFTModal() }) - it('Verify NFT open does not open if no NFT exits [C56126]', () => { + it('Verify NFT open does not open if no NFT exits', () => { nfts.clickOn6thNFT() nfts.verifyNFTModalDoesNotExist() }) - it('Verify multipls NFTs can be selected and reviewed [C56127]', () => { + it('Verify multipls NFTs can be selected and reviewed', () => { nfts.verifyInitialNFTData() nfts.selectNFTs(3) nfts.deselectNFTs([2], 3) diff --git a/cypress/e2e/smoke/pending_actions.cy.js b/cypress/e2e/smoke/pending_actions.cy.js index 6f8a9edc81..c7f2399954 100644 --- a/cypress/e2e/smoke/pending_actions.cy.js +++ b/cypress/e2e/smoke/pending_actions.cy.js @@ -1,7 +1,7 @@ import * as constants from '../../support/constants' import * as safe from '../pages/load_safe.pages' -describe('Pending actions', () => { +describe('Pending actions tests', () => { before(() => { cy.visit(constants.welcomeUrl) // main.acceptCookies() diff --git a/cypress/e2e/smoke/remove_owner.cy.js b/cypress/e2e/smoke/remove_owner.cy.js index b22848c26e..4643b91e1a 100644 --- a/cypress/e2e/smoke/remove_owner.cy.js +++ b/cypress/e2e/smoke/remove_owner.cy.js @@ -10,18 +10,18 @@ describe('Remove Owners tests', () => { cy.contains(owner.safeAccountNonceStr, { timeout: 10000 }) }) - it('Verify that "Remove" icon is visible [C56030]', () => { + it('Verify that "Remove" icon is visible', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_3) owner.verifyRemoveBtnIsEnabled().should('have.length', 2) }) - it('Verify Tooltip displays correct message for Non-Owner [C56037]', () => { + it('Verify Tooltip displays correct message for Non-Owner', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_4) owner.waitForConnectionStatus() owner.verifyRemoveBtnIsDisabled() }) - it('Verify Tooltip displays correct message for disconnected user [C56031]', () => { + it('Verify Tooltip displays correct message for disconnected user', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_3) owner.waitForConnectionStatus() owner.clickOnWalletExpandMoreIcon() @@ -29,13 +29,13 @@ describe('Remove Owners tests', () => { owner.verifyRemoveBtnIsDisabled() }) - it('Verify owner removal form can be opened [C56032]', () => { + it('Verify owner removal form can be opened', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_3) owner.waitForConnectionStatus() owner.openRemoveOwnerWindow(1) }) - it('Verify threshold input displays the upper limit as the current safe number of owners minus one [C56033]', () => { + it('Verify threshold input displays the upper limit as the current safe number of owners minus one', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_3) owner.waitForConnectionStatus() owner.openRemoveOwnerWindow(1) @@ -43,7 +43,7 @@ describe('Remove Owners tests', () => { owner.getThresholdOptions().should('have.length', 1) }) - it('Verify owner deletion confirmation is displayed [C56034]', () => { + it('Verify owner deletion confirmation is displayed', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_3) owner.waitForConnectionStatus() owner.openRemoveOwnerWindow(1) diff --git a/cypress/e2e/smoke/replace_owner.cy.js b/cypress/e2e/smoke/replace_owner.cy.js index b43ec2a8ce..ed48056206 100644 --- a/cypress/e2e/smoke/replace_owner.cy.js +++ b/cypress/e2e/smoke/replace_owner.cy.js @@ -11,36 +11,36 @@ describe('Replace Owners tests', () => { cy.contains(owner.safeAccountNonceStr, { timeout: 10000 }) }) - it('Verify that "Replace" icon is visible [C55998]', () => { + it('Verify that "Replace" icon is visible', () => { owner.verifyReplaceBtnIsEnabled() }) - it('Verify Tooltip displays correct message for Non-Owner [C56016]', () => { + it('Verify Tooltip displays correct message for Non-Owner', () => { cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_2) owner.waitForConnectionStatus() owner.verifyReplaceBtnIsDisabled() }) - it('Verify Tooltip displays correct message for disconnected user [C56006]', () => { + it('Verify Tooltip displays correct message for disconnected user', () => { owner.waitForConnectionStatus() owner.clickOnWalletExpandMoreIcon() owner.clickOnDisconnectBtn() owner.verifyReplaceBtnIsDisabled() }) - it('Verify that the owner replacement form is opened [C56007]', () => { + it('Verify that the owner replacement form is opened', () => { owner.waitForConnectionStatus() owner.openReplaceOwnerWindow() }) - it('Verify max characters in name field [C56008]', () => { + it('Verify max characters in name field', () => { owner.waitForConnectionStatus() owner.openReplaceOwnerWindow() owner.typeOwnerName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) }) - it('Verify that Address input auto-fills with related value [C56009]', () => { + it('Verify that Address input auto-fills with related value', () => { cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_1) addressBook.clickOnCreateEntryBtn() addressBook.typeInName(constants.addresBookContacts.user1.name) @@ -55,7 +55,7 @@ describe('Replace Owners tests', () => { owner.verifyNewOwnerName(constants.addresBookContacts.user1.name) }) - it('Verify that Name field not mandatory. Verify confirmation for owner replacement is displayed [C56011]', () => { + it('Verify that Name field not mandatory. Verify confirmation for owner replacement is displayed', () => { owner.waitForConnectionStatus() owner.openReplaceOwnerWindow() owner.typeOwnerAddress(constants.SEPOLIA_OWNER_2) @@ -63,7 +63,7 @@ describe('Replace Owners tests', () => { owner.verifyConfirmTransactionWindowDisplayed() }) - it('Verify relevant error messages are displayed in Address input [C56012]', () => { + it('Verify relevant error messages are displayed in Address input', () => { owner.waitForConnectionStatus() owner.openReplaceOwnerWindow() owner.typeOwnerAddress(main.generateRandomString(10)) diff --git a/cypress/e2e/smoke/tx_history.cy.js b/cypress/e2e/smoke/tx_history.cy.js index 4ad1e5b929..a9b8309c3f 100644 --- a/cypress/e2e/smoke/tx_history.cy.js +++ b/cypress/e2e/smoke/tx_history.cy.js @@ -20,7 +20,7 @@ describe('Transaction history tests', () => { main.acceptCookies() }) - it('Verify October 29th transactions are displayed [C56128]', () => { + it('Verify October 29th transactions are displayed', () => { const DATE = 'Oct 29, 2023' const NEXT_DATE_LABEL = 'Oct 20, 2023' const amount = '0.00001 ETH' @@ -45,7 +45,7 @@ describe('Transaction history tests', () => { }) }) - it('Verify transaction can be expanded/collapsed [C56129]', () => { + it('Verify transaction can be expanded/collapsed', () => { createTx.clickOnTransactionExpandableItem('Oct 20, 2023', () => { createTx.verifyTransactionStrExists(str1) createTx.verifyTransactionStrExists(str2) From 5399c1a488f387cdd85cb18eeb986f1414d8b25b Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:34:42 +0100 Subject: [PATCH 05/17] fix: Adjust overview widget on dashboard for new design (#2782) * fix: Adjust overview widget on dashboard for new design * fix: Adjust dashboard e2e test * fix: Icon colors in dark mode * fix: Loading state and mobile view * fix: Mobile button alignment --- cypress/e2e/pages/dashboard.pages.js | 9 +- public/images/common/arrow-se.svg | 3 + public/images/common/arrow-top-right.svg | 3 + .../dashboard/Overview/Overview.tsx | 241 +++++++++++------- 4 files changed, 165 insertions(+), 91 deletions(-) create mode 100644 public/images/common/arrow-se.svg create mode 100644 public/images/common/arrow-top-right.svg diff --git a/cypress/e2e/pages/dashboard.pages.js b/cypress/e2e/pages/dashboard.pages.js index 44b0bb4743..02dcdec201 100644 --- a/cypress/e2e/pages/dashboard.pages.js +++ b/cypress/e2e/pages/dashboard.pages.js @@ -5,6 +5,8 @@ const transactionQueueStr = 'Pending transactions' const noTransactionStr = 'This Safe has no queued transactions' const overviewStr = 'Overview' const viewAssetsStr = 'View assets' +const sendStr = 'Send' +const receiveStr = 'Receive' const tokensStr = 'Tokens' const nftStr = 'NFTs' const viewAllStr = 'View all' @@ -27,11 +29,10 @@ export function verifyOverviewWidgetData() { cy.get('@overviewSection').within(() => { // Prefix is separated across elements in EthHashInfo - cy.contains(constants.SEPOLIA_TEST_SAFE_5).should('exist') cy.get('h2').contains('Overview') - cy.get(`a[href="${constants.BALANCE_URL}${encodeURIComponent(constants.SEPOLIA_TEST_SAFE_5)}"]`).contains( - viewAssetsStr, - ) + cy.get(`a[href="${constants.BALANCE_URL}${encodeURIComponent(constants.SEPOLIA_TEST_SAFE_5)}"]`).contains('Tokens') + cy.get('button').contains(sendStr) + cy.get('button').contains(receiveStr) }) } diff --git a/public/images/common/arrow-se.svg b/public/images/common/arrow-se.svg new file mode 100644 index 0000000000..994e469595 --- /dev/null +++ b/public/images/common/arrow-se.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/common/arrow-top-right.svg b/public/images/common/arrow-top-right.svg new file mode 100644 index 0000000000..14866a77da --- /dev/null +++ b/public/images/common/arrow-top-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/dashboard/Overview/Overview.tsx b/src/components/dashboard/Overview/Overview.tsx index 445b860a04..2cf957e96d 100644 --- a/src/components/dashboard/Overview/Overview.tsx +++ b/src/components/dashboard/Overview/Overview.tsx @@ -1,92 +1,94 @@ +import QrCodeButton from '@/components/sidebar/QrCodeButton' +import { TxModalContext } from '@/components/tx-flow' +import NewTxMenu from '@/components/tx-flow/flows/NewTx' +import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps' +import { OVERVIEW_EVENTS, trackEvent } from '@/services/analytics' +import { useAppSelector } from '@/store' +import { selectCurrency } from '@/store/settingsSlice' +import { formatCurrency } from '@/utils/formatNumber' import type { ReactElement } from 'react' -import { useMemo } from 'react' +import { useContext, useMemo } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' -import styled from '@emotion/styled' -import { Box, Button, Grid, Skeleton, Typography } from '@mui/material' +import { Box, Button, Divider, Grid, Skeleton, Typography } from '@mui/material' import { Card, WidgetBody, WidgetContainer } from '../styled' import useSafeInfo from '@/hooks/useSafeInfo' import { useCurrentChain } from '@/hooks/useChains' -import SafeIcon from '@/components/common/SafeIcon' import ChainIndicator from '@/components/common/ChainIndicator' -import EthHashInfo from '@/components/common/EthHashInfo' import { AppRoutes } from '@/config/routes' -import useSafeAddress from '@/hooks/useSafeAddress' import useCollectibles from '@/hooks/useCollectibles' import type { UrlObject } from 'url' import { useVisibleBalances } from '@/hooks/useVisibleBalances' +import AddIcon from '@mui/icons-material/Add' +import ArrowIconNW from '@/public/images/common/arrow-top-right.svg' +import ArrowIconSE from '@/public/images/common/arrow-se.svg' -const IdenticonContainer = styled.div` - position: relative; - margin-bottom: var(--space-2); -` - -const StyledText = styled(Typography)` - margin-top: 8px; - font-size: 24px; - font-weight: bold; -` - -const NetworkLabelContainer = styled.div` - position: absolute; - top: var(--space-3); - right: var(--space-3); - - & span { - bottom: auto; - } -` - -const ValueSkeleton = () => +const ValueSkeleton = () => const SkeletonOverview = ( - - - - - - - - - + + + - + + - + + + - - - Tokens + + + Tokens - - - - - - - NFTs + + NFTs - - - + + + + ) const Overview = (): ReactElement => { + const currency = useAppSelector(selectCurrency) const router = useRouter() - const safeAddress = useSafeAddress() - const { safe, safeLoading, safeLoaded } = useSafeInfo() + const { safeLoading, safeLoaded } = useSafeInfo() const { balances, loading: balancesLoading } = useVisibleBalances() - const [nfts] = useCollectibles() + const [nfts, , nftsLoading] = useCollectibles() const chain = useCurrentChain() const { chainId } = chain || {} + const { setTxFlow } = useContext(TxModalContext) + const [apps] = useRemoteSafeApps() + + const fiatTotal = useMemo( + () => (balances.fiatTotal ? formatCurrency(balances.fiatTotal, currency) : ''), + [currency, balances.fiatTotal], + ) + + const rampSafeApp = apps?.find((app) => app.name === 'Ramp Network') const assetsLink: UrlObject = { pathname: AppRoutes.balances.index, @@ -102,6 +104,12 @@ const Overview = (): ReactElement => { const nftsCount = useMemo(() => (nfts ? `${nfts.next ? '>' : ''}${nfts.results.length}` : ''), [nfts]) const isInitialState = !safeLoaded && !safeLoading + const isLoading = safeLoading || balancesLoading || nftsLoading || isInitialState + + const handleOnSend = () => { + setTxFlow(, undefined, false) + trackEvent(OVERVIEW_EVENTS.NEW_TRANSACTION) + } return ( @@ -110,13 +118,17 @@ const Overview = (): ReactElement => { - {safeLoading || isInitialState ? ( + {isLoading ? ( SkeletonOverview ) : ( - + - - + + + Total asset value + + {fiatTotal} + @@ -124,42 +136,97 @@ const Overview = (): ReactElement => { - - {safeAddress ? ( - - ) : ( - - )} - + - - + + - - Tokens - - {balancesLoading ? : tokenCount} + palette.border.light, + gap: 0.5, + pr: 1, + }} + > + + {balancesLoading ? : tokenCount} + {' '} + + Tokens + + - - - - NFTs - - {nftsCount || } + + + {nftsCount || } + {' '} + + NFTs + + - - - - - - + + + )} + + + + + + - - + + From ef79593325e9fdb87ef6c78f8fe40b4c497d9618 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Thu, 9 Nov 2023 18:17:01 +0100 Subject: [PATCH 06/17] chore: add event label to google login button (#2786) * chore: add event label to google login button * fix: additional requested analytics changes --- src/services/mpc/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/mpc/config.ts b/src/services/mpc/config.ts index 17dce21cc2..5a2f4aa729 100644 --- a/src/services/mpc/config.ts +++ b/src/services/mpc/config.ts @@ -32,7 +32,7 @@ export const SOCIAL_WALLET_OPTIONS: any = (() => { const SOCIAL_WALLET_OPTIONS_STAGING = process.env.NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING || '' try { - return JSON.parse(IS_PRODUCTION ? SOCIAL_WALLET_OPTIONS_PRODUCTION : SOCIAL_WALLET_OPTIONS_STAGING) + return JSON.parse(IS_PRODUCTION ? SOCIAL_WALLET_OPTIONS_PRODUCTION : SOCIAL_WALLET_OPTIONS_PRODUCTION) } catch (error) { console.error('Error parsing SOCIAL_WALLET_OPTIONS', error) return {} From dc9d02225cbba81aecfdeb8f467ac05e6f03d5f0 Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Mon, 13 Nov 2023 00:39:37 +0100 Subject: [PATCH 07/17] fix: Hydration error on the NFTs page (#2788) --- cypress/e2e/smoke/balances.cy.js | 1 + cypress/e2e/smoke/nfts.cy.js | 2 +- src/components/dashboard/Overview/Overview.tsx | 2 +- src/components/nfts/NftGrid/index.tsx | 2 +- src/components/nfts/config.ts | 10 +++++++++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/smoke/balances.cy.js b/cypress/e2e/smoke/balances.cy.js index 0b1da92302..9638173827 100644 --- a/cypress/e2e/smoke/balances.cy.js +++ b/cypress/e2e/smoke/balances.cy.js @@ -16,6 +16,7 @@ describe('Balance tests', () => { cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) main.acceptCookies() cy.contains('Assets') + balances.selectTokenList(balances.tokenListOptions.default) cy.get(balances.balanceSingleRow).should('have.length.lessThan', ASSETS_LENGTH) balances.selectTokenList(balances.tokenListOptions.allTokens) cy.get(balances.balanceSingleRow).should('have.length', ASSETS_LENGTH) diff --git a/cypress/e2e/smoke/nfts.cy.js b/cypress/e2e/smoke/nfts.cy.js index 1ecc5c1c20..01680d3f72 100644 --- a/cypress/e2e/smoke/nfts.cy.js +++ b/cypress/e2e/smoke/nfts.cy.js @@ -9,7 +9,7 @@ const nftsTokenID = 'CF' describe('NFTs tests', () => { beforeEach(() => { cy.clearLocalStorage() - cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) + cy.visit(constants.balanceNftsUrl + constants.SEPOLIA_TEST_SAFE_5) main.acceptCookies() nfts.clickOnNftsTab() }) diff --git a/src/components/dashboard/Overview/Overview.tsx b/src/components/dashboard/Overview/Overview.tsx index 2cf957e96d..42e9144d3d 100644 --- a/src/components/dashboard/Overview/Overview.tsx +++ b/src/components/dashboard/Overview/Overview.tsx @@ -200,7 +200,7 @@ const Overview = (): ReactElement => { variant="contained" color="primary" startIcon={} - sx={{ height: 1 }} + sx={{ minHeight: '40px' }} fullWidth > Buy crypto diff --git a/src/components/nfts/NftGrid/index.tsx b/src/components/nfts/NftGrid/index.tsx index 44905957ff..13e79cb71b 100644 --- a/src/components/nfts/NftGrid/index.tsx +++ b/src/components/nfts/NftGrid/index.tsx @@ -90,7 +90,7 @@ const NftGrid = ({ onPreview, }: NftsTableProps): ReactElement => { const chainId = useChainId() - const linkTemplates = nftPlatforms[chainId] + const linkTemplates = nftPlatforms[chainId] || [] // Filter string const [filter, setFilter] = useState('') diff --git a/src/components/nfts/config.ts b/src/components/nfts/config.ts index 6e4610ace7..f03045ec1e 100644 --- a/src/components/nfts/config.ts +++ b/src/components/nfts/config.ts @@ -66,7 +66,15 @@ export const nftPlatforms: Record> = { { title: 'OpenSea', logo: '/images/common/nft-opensea.svg', - getUrl: (item) => `https://testnets.opensea.io/assets/${item.address}/${item.id}`, + getUrl: (item) => `https://testnets.opensea.io/assets/goerli/${item.address}/${item.id}`, + }, + ], + + [chains.sep]: [ + { + title: 'OpenSea', + logo: '/images/common/nft-opensea.svg', + getUrl: (item) => `https://testnets.opensea.io/assets/sepolia/${item.address}/${item.id}`, }, ], From ba0ffdeccb76da22582cb7f927865650effe5c67 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:05:12 +0100 Subject: [PATCH 08/17] Fix: show relays even when 0 relays left (#2791) * Fix: show relays even when 0 relays left * Do not show block if relaying not enabled at all * Restore default option to Relay * Restore version --- package.json | 2 +- .../tx/ExecutionMethodSelector/index.tsx | 2 +- .../tx/SignOrExecuteForm/ExecuteForm.tsx | 25 +++--- src/components/tx/SponsoredBy/index.tsx | 87 ++++++++++++------- src/hooks/useWalletCanRelay.ts | 8 +- 5 files changed, 80 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 26ac24be3c..f2cabbfaf3 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "homepage": "https://github.com/safe-global/safe-wallet-web", "license": "GPL-3.0", "type": "module", - "version": "1.22.0", + "version": "1.21.0", "scripts": { "dev": "next dev", "start": "next dev", diff --git a/src/components/tx/ExecutionMethodSelector/index.tsx b/src/components/tx/ExecutionMethodSelector/index.tsx index 95e56b9d4a..baae6e7855 100644 --- a/src/components/tx/ExecutionMethodSelector/index.tsx +++ b/src/components/tx/ExecutionMethodSelector/index.tsx @@ -71,7 +71,7 @@ export const ExecutionMethodSelector = ({ - {shouldRelay && relays ? : null} + ) } diff --git a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx index 72ae840fc6..628c5877f9 100644 --- a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx +++ b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx @@ -12,7 +12,6 @@ import { useIsExecutionLoop, useTxActions } from './hooks' import { useRelaysBySafe } from '@/hooks/useRemainingRelays' import useWalletCanRelay from '@/hooks/useWalletCanRelay' import { ExecutionMethod, ExecutionMethodSelector } from '../ExecutionMethodSelector' -import { hasRemainingRelays } from '@/utils/relaying' import type { SignOrExecuteProps } from '.' import type { SafeTransaction } from '@safe-global/safe-core-sdk-types' import { TxModalContext } from '@/components/tx-flow' @@ -53,15 +52,13 @@ const ExecuteForm = ({ // Check that the transaction is executable const isExecutionLoop = useIsExecutionLoop() - // We default to relay, but the option is only shown if we canRelay - const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) - // SC wallets can relay fully signed transactions - const [walletCanRelay] = useWalletCanRelay(safeTx) - + const [canWalletRelay] = useWalletCanRelay(safeTx) + // We default to relay + const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) // The transaction can/will be relayed - const canRelay = walletCanRelay && hasRemainingRelays(relays) - const willRelay = canRelay && executionMethod === ExecutionMethod.RELAY + const willRelay = executionMethod === ExecutionMethod.RELAY + const hasRelays = !!relays?.remaining // Estimate gas limit const { gasLimit, gasLimitError } = useGasLimit(safeTx) @@ -102,12 +99,18 @@ const ExecuteForm = ({ const cannotPropose = !isOwner && !onlyExecute const submitDisabled = - !safeTx || !isSubmittable || disableSubmit || isValidExecutionLoading || isExecutionLoop || cannotPropose + !safeTx || + !isSubmittable || + disableSubmit || + isValidExecutionLoading || + isExecutionLoop || + cannotPropose || + (willRelay && !hasRelays) return ( <>
-
+
- {canRelay && ( + {canWalletRelay && (
{ +const SponsoredBy = ({ + relays, + tooltip, + shouldRelay, +}: { + relays?: RelayResponse + tooltip?: string + shouldRelay: boolean +}) => { const chain = useCurrentChain() return ( -
- - - Sponsored by + + {shouldRelay ? ( +
+ + + Sponsored by + + + {chain?.chainName} + + {chain?.chainName} + + + {tooltip ? ( + + + + + + ) : null} + + + + Transactions per hour:{' '} + + {relays?.remaining ?? 0} of {relays?.limit ?? 0} + + {relays && !relays.remaining && ( + + {' '} + — will reset in the next hour + + )} - {chain?.chainName} +
+ ) : ( +
- {chain?.chainName} + Pay gas from the connected wallet + + + + Please make sure your wallet has sufficient funds. - {tooltip ? ( - - - - - - ) : null} - - - - Transactions per hour:{' '} - - {relays.remaining} of {relays.limit} - - -
+
+ )}
) } diff --git a/src/hooks/useWalletCanRelay.ts b/src/hooks/useWalletCanRelay.ts index e7261e0226..d451855b3c 100644 --- a/src/hooks/useWalletCanRelay.ts +++ b/src/hooks/useWalletCanRelay.ts @@ -4,14 +4,18 @@ import useWallet from '@/hooks/wallets/useWallet' import { isSmartContractWallet } from '@/utils/wallets' import { Errors, logError } from '@/services/exceptions' import { type SafeTransaction } from '@safe-global/safe-core-sdk-types' +import { FEATURES, hasFeature } from '@/utils/chains' +import { useCurrentChain } from './useChains' const useWalletCanRelay = (tx: SafeTransaction | undefined) => { const { safe } = useSafeInfo() const wallet = useWallet() + const chain = useCurrentChain() + const isFeatureEnabled = chain && hasFeature(chain, FEATURES.RELAYING) const hasEnoughSignatures = tx && tx.signatures.size >= safe.threshold return useAsync(() => { - if (!tx || !wallet) return + if (!isFeatureEnabled || !tx || !wallet) return return isSmartContractWallet(wallet) .then((isSCWallet) => { @@ -23,7 +27,7 @@ const useWalletCanRelay = (tx: SafeTransaction | undefined) => { logError(Errors._106, err.message) return false }) - }, [hasEnoughSignatures, tx, wallet]) + }, [isFeatureEnabled, hasEnoughSignatures, tx, wallet]) } export default useWalletCanRelay From d95a9438e42b36b546d7210e9b82fc8e605f79b3 Mon Sep 17 00:00:00 2001 From: katspaugh Date: Mon, 13 Nov 2023 11:05:40 +0100 Subject: [PATCH 09/17] 1.22.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2cabbfaf3..26ac24be3c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "homepage": "https://github.com/safe-global/safe-wallet-web", "license": "GPL-3.0", "type": "module", - "version": "1.21.0", + "version": "1.22.0", "scripts": { "dev": "next dev", "start": "next dev", From 937dabb1ee603236bc5588bb0fd3d95064a7f750 Mon Sep 17 00:00:00 2001 From: schmanu Date: Mon, 13 Nov 2023 12:13:53 +0100 Subject: [PATCH 10/17] fix: different configs for staging and prod --- src/services/mpc/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/mpc/config.ts b/src/services/mpc/config.ts index 5a2f4aa729..17dce21cc2 100644 --- a/src/services/mpc/config.ts +++ b/src/services/mpc/config.ts @@ -32,7 +32,7 @@ export const SOCIAL_WALLET_OPTIONS: any = (() => { const SOCIAL_WALLET_OPTIONS_STAGING = process.env.NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING || '' try { - return JSON.parse(IS_PRODUCTION ? SOCIAL_WALLET_OPTIONS_PRODUCTION : SOCIAL_WALLET_OPTIONS_PRODUCTION) + return JSON.parse(IS_PRODUCTION ? SOCIAL_WALLET_OPTIONS_PRODUCTION : SOCIAL_WALLET_OPTIONS_STAGING) } catch (error) { console.error('Error parsing SOCIAL_WALLET_OPTIONS', error) return {} From f3fc362f7cc196d58dbc3c23337e24927fc82a25 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:44:56 +0100 Subject: [PATCH 11/17] Fix: prefix switch for receive QR (#2795) --- .../balances/AssetsTable/NoAssets.tsx | 76 +++++++++++++++++++ src/components/balances/AssetsTable/index.tsx | 61 +-------------- 2 files changed, 78 insertions(+), 59 deletions(-) create mode 100644 src/components/balances/AssetsTable/NoAssets.tsx diff --git a/src/components/balances/AssetsTable/NoAssets.tsx b/src/components/balances/AssetsTable/NoAssets.tsx new file mode 100644 index 0000000000..ebf337c6f6 --- /dev/null +++ b/src/components/balances/AssetsTable/NoAssets.tsx @@ -0,0 +1,76 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' +import { Box, Button, FormControlLabel, Grid, Paper, Switch, Typography } from '@mui/material' +import EthHashInfo from '@/components/common/EthHashInfo' +import QRCode from '@/components/common/QRCode' +import { AppRoutes } from '@/config/routes' +import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps' +import { useCurrentChain } from '@/hooks/useChains' +import useSafeAddress from '@/hooks/useSafeAddress' +import { useAppDispatch, useAppSelector } from '@/store' +import { selectSettings, setQrShortName } from '@/store/settingsSlice' +import AddIcon from '@mui/icons-material/Add' + +const NoAssets = () => { + const router = useRouter() + const safeAddress = useSafeAddress() + const chain = useCurrentChain() + const dispatch = useAppDispatch() + const settings = useAppSelector(selectSettings) + const qrPrefix = settings.shortName.qr ? `${chain?.shortName}:` : '' + const qrCode = `${qrPrefix}${safeAddress}` + const [apps] = useRemoteSafeApps() + + // @FIXME: use tags instead of name + const rampSafeApp = apps?.find((app) => app.name === 'Ramp Network') + + return ( + + + +
+ + + +
+ + dispatch(setQrShortName(e.target.checked))} /> + } + label={<>QR code with chain prefix} + /> +
+ + + + Add funds to get started + + + + Add funds directly from your bank account or copy your address to send tokens from a different account. + + + + + + + {rampSafeApp && ( + + + + + + )} + +
+
+ ) +} + +export default NoAssets diff --git a/src/components/balances/AssetsTable/index.tsx b/src/components/balances/AssetsTable/index.tsx index 599c8c2704..57c0a28c16 100644 --- a/src/components/balances/AssetsTable/index.tsx +++ b/src/components/balances/AssetsTable/index.tsx @@ -1,15 +1,5 @@ -import EthHashInfo from '@/components/common/EthHashInfo' -import QRCode from '@/components/common/QRCode' -import { AppRoutes } from '@/config/routes' -import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps' -import { useCurrentChain } from '@/hooks/useChains' -import useSafeAddress from '@/hooks/useSafeAddress' -import { useAppSelector } from '@/store' -import { selectSettings } from '@/store/settingsSlice' -import Link from 'next/link' -import { useRouter } from 'next/router' import { type ReactElement, useMemo, useContext } from 'react' -import { Button, Tooltip, Typography, SvgIcon, IconButton, Box, Checkbox, Skeleton, Paper, Grid } from '@mui/material' +import { Button, Tooltip, Typography, SvgIcon, IconButton, Box, Checkbox, Skeleton } from '@mui/material' import type { TokenInfo } from '@safe-global/safe-gateway-typescript-sdk' import { TokenType } from '@safe-global/safe-gateway-typescript-sdk' import css from './styles.module.css' @@ -30,7 +20,7 @@ import CheckWallet from '@/components/common/CheckWallet' import useSpendingLimit from '@/hooks/useSpendingLimit' import { TxModalContext } from '@/components/tx-flow' import TokenTransferFlow from '@/components/tx-flow/flows/TokenTransfer' -import AddIcon from '@mui/icons-material/Add' +import NoAssets from './NoAssets' const skeletonCells: EnhancedTableProps['rows'][0]['cells'] = { asset: { @@ -271,51 +261,4 @@ const AssetsTable = ({ ) } -const NoAssets = () => { - const router = useRouter() - const safeAddress = useSafeAddress() - const chain = useCurrentChain() - const settings = useAppSelector(selectSettings) - const qrPrefix = settings.shortName.qr ? `${chain?.shortName}:` : '' - const qrCode = `${qrPrefix}${safeAddress}` - const [apps] = useRemoteSafeApps() - - const rampSafeApp = apps?.find((app) => app.name === 'Ramp Network') - - return ( - - - - - - - - - - Add funds to get started - - - Add funds directly from your bank account or copy your address to send tokens from a different account. - - - - - {rampSafeApp && ( - - - - - - )} - - - - ) -} - export default AssetsTable From 8419d23da3720e2c53f21f82a94c56205324d795 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Mon, 13 Nov 2023 15:51:48 +0100 Subject: [PATCH 12/17] fix: allow execution of web assembly (#2797) * fix: allow execution of web assembly * fix: CSP string --- src/config/securityHeaders.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/securityHeaders.ts b/src/config/securityHeaders.ts index 69dd425090..8d3faa3bb1 100644 --- a/src/config/securityHeaders.ts +++ b/src/config/securityHeaders.ts @@ -15,7 +15,7 @@ export const ContentSecurityPolicy = ` script-src 'self' https://www.google-analytics.com https://ssl.google-analytics.com 'unsafe-inline' https://*.getbeamer.com https://www.googletagmanager.com https://*.ingest.sentry.io https://sentry.io ${ !IS_PRODUCTION || /* TODO: remove after moving cypress to görli and testing in staging again!! */ CYPRESS_MNEMONIC ? "'unsafe-eval'" - : '' + : "'wasm-unsafe-eval'" }; frame-src *; style-src 'self' 'unsafe-inline' https://*.getbeamer.com https://*.googleapis.com; From c8752b58731a15107a15c099a8948ce6cf9baffe Mon Sep 17 00:00:00 2001 From: katspaugh Date: Mon, 13 Nov 2023 15:52:05 +0100 Subject: [PATCH 13/17] 1.22.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26ac24be3c..60669cabad 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "homepage": "https://github.com/safe-global/safe-wallet-web", "license": "GPL-3.0", "type": "module", - "version": "1.22.0", + "version": "1.22.1", "scripts": { "dev": "next dev", "start": "next dev", From ced3211d6d5552e7c9087a476d6cd9f0f0db82ec Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:39:07 +0100 Subject: [PATCH 14/17] Fix: revert relaying message (#2799) --- package.json | 2 +- .../tx/ExecutionMethodSelector/index.tsx | 2 +- .../tx/SignOrExecuteForm/ExecuteForm.tsx | 25 +++--- src/components/tx/SponsoredBy/index.tsx | 87 +++++++------------ src/hooks/useWalletCanRelay.ts | 8 +- 5 files changed, 44 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index 60669cabad..329f71c72e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "homepage": "https://github.com/safe-global/safe-wallet-web", "license": "GPL-3.0", "type": "module", - "version": "1.22.1", + "version": "1.22.2", "scripts": { "dev": "next dev", "start": "next dev", diff --git a/src/components/tx/ExecutionMethodSelector/index.tsx b/src/components/tx/ExecutionMethodSelector/index.tsx index baae6e7855..95e56b9d4a 100644 --- a/src/components/tx/ExecutionMethodSelector/index.tsx +++ b/src/components/tx/ExecutionMethodSelector/index.tsx @@ -71,7 +71,7 @@ export const ExecutionMethodSelector = ({
- + {shouldRelay && relays ? : null} ) } diff --git a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx index 628c5877f9..72ae840fc6 100644 --- a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx +++ b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx @@ -12,6 +12,7 @@ import { useIsExecutionLoop, useTxActions } from './hooks' import { useRelaysBySafe } from '@/hooks/useRemainingRelays' import useWalletCanRelay from '@/hooks/useWalletCanRelay' import { ExecutionMethod, ExecutionMethodSelector } from '../ExecutionMethodSelector' +import { hasRemainingRelays } from '@/utils/relaying' import type { SignOrExecuteProps } from '.' import type { SafeTransaction } from '@safe-global/safe-core-sdk-types' import { TxModalContext } from '@/components/tx-flow' @@ -52,13 +53,15 @@ const ExecuteForm = ({ // Check that the transaction is executable const isExecutionLoop = useIsExecutionLoop() - // SC wallets can relay fully signed transactions - const [canWalletRelay] = useWalletCanRelay(safeTx) - // We default to relay + // We default to relay, but the option is only shown if we canRelay const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) + + // SC wallets can relay fully signed transactions + const [walletCanRelay] = useWalletCanRelay(safeTx) + // The transaction can/will be relayed - const willRelay = executionMethod === ExecutionMethod.RELAY - const hasRelays = !!relays?.remaining + const canRelay = walletCanRelay && hasRemainingRelays(relays) + const willRelay = canRelay && executionMethod === ExecutionMethod.RELAY // Estimate gas limit const { gasLimit, gasLimitError } = useGasLimit(safeTx) @@ -99,18 +102,12 @@ const ExecuteForm = ({ const cannotPropose = !isOwner && !onlyExecute const submitDisabled = - !safeTx || - !isSubmittable || - disableSubmit || - isValidExecutionLoading || - isExecutionLoop || - cannotPropose || - (willRelay && !hasRelays) + !safeTx || !isSubmittable || disableSubmit || isValidExecutionLoading || isExecutionLoop || cannotPropose return ( <> -
+
- {canWalletRelay && ( + {canRelay && (
{ +const SponsoredBy = ({ relays, tooltip }: { relays: RelayResponse; tooltip?: string }) => { const chain = useCurrentChain() return ( - - {shouldRelay ? ( -
- - - Sponsored by - - - {chain?.chainName} - - {chain?.chainName} - - - {tooltip ? ( - - - - - - ) : null} - - - - Transactions per hour:{' '} - - {relays?.remaining ?? 0} of {relays?.limit ?? 0} - - {relays && !relays.remaining && ( - - {' '} - — will reset in the next hour - - )} - -
- ) : ( -
+
+ - Pay gas from the connected wallet + Sponsored by - - - Please make sure your wallet has sufficient funds. + {chain?.chainName} + + {chain?.chainName} -
- )} + {tooltip ? ( + + + + + + ) : null} + + + + Transactions per hour:{' '} + + {relays.remaining} of {relays.limit} + + +
) } diff --git a/src/hooks/useWalletCanRelay.ts b/src/hooks/useWalletCanRelay.ts index d451855b3c..e7261e0226 100644 --- a/src/hooks/useWalletCanRelay.ts +++ b/src/hooks/useWalletCanRelay.ts @@ -4,18 +4,14 @@ import useWallet from '@/hooks/wallets/useWallet' import { isSmartContractWallet } from '@/utils/wallets' import { Errors, logError } from '@/services/exceptions' import { type SafeTransaction } from '@safe-global/safe-core-sdk-types' -import { FEATURES, hasFeature } from '@/utils/chains' -import { useCurrentChain } from './useChains' const useWalletCanRelay = (tx: SafeTransaction | undefined) => { const { safe } = useSafeInfo() const wallet = useWallet() - const chain = useCurrentChain() - const isFeatureEnabled = chain && hasFeature(chain, FEATURES.RELAYING) const hasEnoughSignatures = tx && tx.signatures.size >= safe.threshold return useAsync(() => { - if (!isFeatureEnabled || !tx || !wallet) return + if (!tx || !wallet) return return isSmartContractWallet(wallet) .then((isSCWallet) => { @@ -27,7 +23,7 @@ const useWalletCanRelay = (tx: SafeTransaction | undefined) => { logError(Errors._106, err.message) return false }) - }, [isFeatureEnabled, hasEnoughSignatures, tx, wallet]) + }, [hasEnoughSignatures, tx, wallet]) } export default useWalletCanRelay From b356d75cd4eba5af0b5aedefd7d8fc9b50869b3b Mon Sep 17 00:00:00 2001 From: Michael <30682308+mike10ca@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:30:32 +0100 Subject: [PATCH 15/17] Tests: Add test IDs and modify NFT tests (#2804) * Add test IDs amd modify nft tests --- cypress/e2e/pages/main.page.js | 1 + cypress/e2e/pages/nfts.pages.js | 76 +++++++++++-------- cypress/e2e/safe-apps/tx-builder.spec.cy.js | 2 +- cypress/e2e/smoke/nfts.cy.js | 10 +-- src/components/common/ModalDialog/index.tsx | 4 +- src/components/nfts/NftGrid/index.tsx | 18 ++++- src/components/nfts/NftSendForm/index.tsx | 1 + .../tx-flow/common/TxLayout/index.tsx | 17 ++++- .../flows/NftTransfer/SendNftBatch.tsx | 21 ++++- .../tx/SignOrExecuteForm/SignForm.tsx | 2 +- 10 files changed, 105 insertions(+), 47 deletions(-) diff --git a/cypress/e2e/pages/main.page.js b/cypress/e2e/pages/main.page.js index 438797b93c..2c6df5a423 100644 --- a/cypress/e2e/pages/main.page.js +++ b/cypress/e2e/pages/main.page.js @@ -1,6 +1,7 @@ import * as constants from '../../support/constants' const acceptSelection = 'Accept selection' +export const modalDialogCloseBtn = '[data-testid="modal-dialog-close-btn"]' export function clickOnSideMenuItem(item) { cy.get('p').contains(item).click() diff --git a/cypress/e2e/pages/nfts.pages.js b/cypress/e2e/pages/nfts.pages.js index 85775098cb..82796786f6 100644 --- a/cypress/e2e/pages/nfts.pages.js +++ b/cypress/e2e/pages/nfts.pages.js @@ -1,8 +1,22 @@ import * as constants from '../../support/constants' +import * as main from '../pages/main.page' -const nftModal = 'div[role="dialog"]' -const nftModalCloseBtn = 'button[aria-label="close"]' +const nftModalTitle = '[data-testid="modal-title"]' +const nftModal = '[data-testid="modal-view"]' + +const nftModalCloseBtn = main.modalDialogCloseBtn const recipientInput = 'input[name="recipient"]' +const nftsRow = '[data-testid^="nfts-table-row"]' +const inactiveNftIcon = '[data-testid="nft-icon-border"]' +const activeNftIcon = '[data-testid="nft-icon-primary"]' +const nftCheckBox = (index) => `[data-testid="nft-checkbox-${index}"] > input` +const activeSendNFTBtn = '[data-testid="nft-send-btn-false"]' +const modalTitle = '[data-testid="modal-title"]' +const modalHeader = '[data-testid="modal-header"]' +const modalSelectedNFTs = '[data-testid="selected-nfts"]' +const nftItemList = '[data-testid="nft-item-list"]' +const nftItemNane = '[data-testid="nft-item-name"]' +const signBtn = '[data-testid="sign-btn"]' const noneNFTSelected = '0 NFTs selected' const sendNFTStr = 'Send NFTs' @@ -19,7 +33,8 @@ export function clickOnNftsTab() { cy.get('p').contains('NFTs').click() } function verifyTableRows(number) { - cy.get('tbody tr').should('have.length', number) + cy.scrollTo('bottom').wait(500) + cy.get(nftsRow).should('have.length.at.least', number) } export function verifyNFTNumber(number) { @@ -27,44 +42,48 @@ export function verifyNFTNumber(number) { } export function verifyDataInTable(name, address, tokenID) { - cy.get('tbody tr:first-child').contains('td:first-child', name) - cy.get('tbody tr:first-child').contains('td:first-child', address) - cy.get('tbody tr:first-child').contains('td:nth-child(2)', tokenID) + cy.get(nftsRow).contains(name) + cy.get(nftsRow).contains(address) + cy.get(nftsRow).contains(tokenID) } -export function openNFT(index) { - cy.get('tbody').within(() => { - cy.get('tr').eq(index).click() - }) +export function waitForNftItems(count) { + cy.get(nftsRow).should('have.length.at.least', count) +} + +export function openActiveNFT(index) { + cy.get(activeNftIcon).eq(index).click() } export function verifyNameInNFTModal(name) { - cy.get(nftModal).contains(name) + cy.get(nftModalTitle).contains(name) } export function verifySelectedNetwrokSepolia() { - cy.get(nftModal).contains(constants.networks.sepolia) + cy.get(nftModal).within(() => { + cy.get(nftModalTitle).contains(constants.networks.sepolia) + }) } export function verifyNFTModalLink(link) { - cy.get(nftModal).contains(`a[href="${link}"]`, 'View on OpenSea') + cy.get(nftModalTitle).contains(`a[href="${link}"]`, 'View on OpenSea') } export function closeNFTModal() { cy.get(nftModalCloseBtn).click() - cy.get(nftModal).should('not.exist') + cy.get(nftModalTitle).should('not.exist') } -export function clickOn6thNFT() { - cy.get('tbody tr:nth-child(6) td:nth-child(2)').click() +export function clickOnInactiveNFT() { + cy.get(inactiveNftIcon).eq(0).click() } export function verifyNFTModalDoesNotExist() { - cy.get(nftModal).should('not.exist') + cy.get(nftModalTitle).should('not.exist') } export function selectNFTs(numberOfNFTs) { for (let i = 1; i <= numberOfNFTs; i++) { - cy.get(`tbody tr:nth-child(${i}) input[type="checkbox"]`).click() + cy.get(nftCheckBox(i)).click() cy.contains(`${i} NFT${i > 1 ? 's' : ''} selected`) } cy.contains('button', `Send ${numberOfNFTs} NFT${numberOfNFTs > 1 ? 's' : ''}`) @@ -73,8 +92,8 @@ export function selectNFTs(numberOfNFTs) { export function deselectNFTs(checkboxIndexes, checkedItems) { let total = checkedItems - checkboxIndexes.length - checkboxIndexes.forEach((index) => { - cy.get(`tbody tr:nth-child(${index}) input[type="checkbox"]`).uncheck() + checkboxIndexes.forEach((i) => { + cy.get(nftCheckBox(i)).uncheck() }) cy.contains(`${total} NFT${total !== 1 ? 's' : ''} selected`) @@ -88,14 +107,12 @@ export function verifyInitialNFTData() { cy.contains('button[disabled]', 'Send') } -export function sendNFT(numberOfCheckedNFTs) { - cy.contains('button', `Send ${numberOfCheckedNFTs} NFT${numberOfCheckedNFTs !== 1 ? 's' : ''}`).click() +export function sendNFT() { + cy.get(activeSendNFTBtn).click() } export function verifyNFTModalData() { - cy.contains(sendNFTStr) - cy.contains(recipientAddressStr) - cy.contains(selectedNFTStr) + main.verifyElementsExist([modalTitle, modalHeader, modalSelectedNFTs]) } export function typeRecipientAddress(address) { @@ -107,11 +124,10 @@ export function clikOnNextBtn() { } export function verifyReviewModalData(NFTcount) { - cy.contains(sendStr) - cy.contains(toStr) - cy.wait(1000) - cy.get(`b:contains(${transferFromStr})`).should('have.length', NFTcount) - cy.contains('button:not([disabled])', signBtnStr) + main.verifyElementsExist([nftItemList]) + main.verifyElementsCount(nftItemNane, NFTcount) + cy.get(signBtn).should('not.be.disabled') + if (NFTcount > 1) { const numbersArr = Array.from({ length: NFTcount }, (_, index) => index + 1) numbersArr.forEach((number) => { diff --git a/cypress/e2e/safe-apps/tx-builder.spec.cy.js b/cypress/e2e/safe-apps/tx-builder.spec.cy.js index da1bd43e40..057d12a217 100644 --- a/cypress/e2e/safe-apps/tx-builder.spec.cy.js +++ b/cypress/e2e/safe-apps/tx-builder.spec.cy.js @@ -147,7 +147,7 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { getBody().findByText(safeapps.sendBatchStr).click() }) cy.get('p').contains('1').should('exist') - cy.get('p').contains('2').should('be.visible') + cy.get('p').contains('2').should('exist') }) it('Verify a batch cannot be created with invalid address', () => { diff --git a/cypress/e2e/smoke/nfts.cy.js b/cypress/e2e/smoke/nfts.cy.js index 01680d3f72..4a39e1a545 100644 --- a/cypress/e2e/smoke/nfts.cy.js +++ b/cypress/e2e/smoke/nfts.cy.js @@ -11,11 +11,11 @@ describe('NFTs tests', () => { cy.clearLocalStorage() cy.visit(constants.balanceNftsUrl + constants.SEPOLIA_TEST_SAFE_5) main.acceptCookies() - nfts.clickOnNftsTab() + nfts.waitForNftItems(2) }) it('Verify that NFTs exist in the table', () => { - nfts.verifyNFTNumber(20) + nfts.verifyNFTNumber(10) }) it('Verify NFT row contains data', () => { @@ -23,14 +23,14 @@ describe('NFTs tests', () => { }) it('Verify NFT preview window can be opened', () => { - nfts.openNFT(0) + nfts.openActiveNFT(0) nfts.verifyNameInNFTModal(nftsTokenID) nfts.verifySelectedNetwrokSepolia() nfts.closeNFTModal() }) it('Verify NFT open does not open if no NFT exits', () => { - nfts.clickOn6thNFT() + nfts.clickOnInactiveNFT() nfts.verifyNFTModalDoesNotExist() }) @@ -38,7 +38,7 @@ describe('NFTs tests', () => { nfts.verifyInitialNFTData() nfts.selectNFTs(3) nfts.deselectNFTs([2], 3) - nfts.sendNFT(2) + nfts.sendNFT() nfts.verifyNFTModalData() nfts.typeRecipientAddress(constants.SEPOLIA_TEST_SAFE_4) nfts.clikOnNextBtn() diff --git a/src/components/common/ModalDialog/index.tsx b/src/components/common/ModalDialog/index.tsx index cede7fe2cf..c9d32eafe6 100644 --- a/src/components/common/ModalDialog/index.tsx +++ b/src/components/common/ModalDialog/index.tsx @@ -20,12 +20,13 @@ interface DialogTitleProps { export const ModalDialogTitle = ({ children, onClose, hideChainIndicator = false, ...other }: DialogTitleProps) => { return ( - + {children} {!hideChainIndicator && } {onClose ? ( { onClose(e, 'backdropClick') @@ -56,6 +57,7 @@ const ModalDialog = ({ return ( e.stopPropagation() const NftIndicator = ({ color }: { color: SvgIconProps['color'] }) => ( - + ) const activeNftIcon = @@ -191,7 +199,7 @@ const NftGrid = ({ const sx = item.imageUri ? { cursor: 'pointer' } : undefined return ( - + {/* Collection name */} @@ -233,7 +241,11 @@ const NftGrid = ({ {/* Checkbox */} - onCheckboxClick(e, item)} /> + onCheckboxClick(e, item)} + /> {/* Insert the children at the end of the table */} {index === filteredNfts.length - 1 && children} diff --git a/src/components/nfts/NftSendForm/index.tsx b/src/components/nfts/NftSendForm/index.tsx index edfb4f5735..9665dcd4e3 100644 --- a/src/components/nfts/NftSendForm/index.tsx +++ b/src/components/nfts/NftSendForm/index.tsx @@ -32,6 +32,7 @@ const NftSendForm = ({ selectedNfts }: NftSendFormProps): ReactElement => { {(isOk) => ( )} diff --git a/src/components/tx-flow/flows/NftTransfer/SendNftBatch.tsx b/src/components/tx-flow/flows/NftTransfer/SendNftBatch.tsx index 3aeee4fab7..b058444f56 100644 --- a/src/components/tx-flow/flows/NftTransfer/SendNftBatch.tsx +++ b/src/components/tx-flow/flows/NftTransfer/SendNftBatch.tsx @@ -37,7 +37,14 @@ const NftItem = ({ image, name, description }: { image: string; name: string; de - + {name} @@ -59,7 +66,15 @@ const NftItem = ({ image, name, description }: { image: string; name: string; de export const NftItems = ({ tokens }: { tokens: SafeCollectibleResponse[] }) => { return ( - + {tokens.map((token) => ( { )} - + Selected NFTs diff --git a/src/components/tx/SignOrExecuteForm/SignForm.tsx b/src/components/tx/SignOrExecuteForm/SignForm.tsx index 03cf9cbaff..410254da8e 100644 --- a/src/components/tx/SignOrExecuteForm/SignForm.tsx +++ b/src/components/tx/SignOrExecuteForm/SignForm.tsx @@ -103,7 +103,7 @@ const SignForm = ({ {/* Submit button */} {(isOk) => ( - )} From 34f1baeabd213db0cf877c9724f7d6d1986d17a2 Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:49:35 +0100 Subject: [PATCH 16/17] chore: Analyze NextJS bundle size action (#2803) * chore: Analyze NextJS bundle size action * fix: Remove redundant caching * fix: Remove working directory --- .github/workflows/nextjs_bundle_analysis.yml | 98 ++++++++++++++++++++ package.json | 6 ++ 2 files changed, 104 insertions(+) create mode 100644 .github/workflows/nextjs_bundle_analysis.yml diff --git a/.github/workflows/nextjs_bundle_analysis.yml b/.github/workflows/nextjs_bundle_analysis.yml new file mode 100644 index 0000000000..2e9672b0df --- /dev/null +++ b/.github/workflows/nextjs_bundle_analysis.yml @@ -0,0 +1,98 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +name: 'Next.js Bundle Analysis' + +on: + pull_request: + push: + branches: + - dev + +permissions: + contents: read # for checkout repository + actions: read # for fetching base branch bundle stats + pull-requests: write # for comments + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install dependencies + uses: ./.github/workflows/yarn + + - name: Build next.js app + uses: ./.github/workflows/build + with: + secrets: ${{ toJSON(secrets) }} + + # Here's the first place where next-bundle-analysis' own script is used + # This step pulls the raw bundle stats for the current bundle + - name: Analyze bundle + run: npx -p nextjs-bundle-analysis report + + - name: Upload bundle + uses: actions/upload-artifact@v3 + with: + name: bundle + path: .next/analyze/__bundle_analysis.json + + - name: Download base branch bundle stats + uses: dawidd6/action-download-artifact@v2 + if: success() && github.event.number + with: + workflow: nextjs_bundle_analysis.yml + branch: ${{ github.event.pull_request.base.ref }} + path: .next/analyze/base + + # And here's the second place - this runs after we have both the current and + # base branch bundle stats, and will compare them to determine what changed. + # There are two configurable arguments that come from package.json: + # + # - budget: optional, set a budget (bytes) against which size changes are measured + # it's set to 350kb here by default, as informed by the following piece: + # https://infrequently.org/2021/03/the-performance-inequality-gap/ + # + # - red-status-percentage: sets the percent size increase where you get a red + # status indicator, defaults to 20% + # + # Either of these arguments can be changed or removed by editing the `nextBundleAnalysis` + # entry in your package.json file. + - name: Compare with base branch bundle + if: success() && github.event.number + run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare + + - name: Get Comment Body + id: get-comment-body + if: success() && github.event.number + # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings + run: | + echo "body<> $GITHUB_OUTPUT + echo "$(cat .next/analyze/__bundle_analysis_comment.txt)" >> $GITHUB_OUTPUT + echo EOF >> $GITHUB_OUTPUT + + - name: Find Comment + uses: peter-evans/find-comment@v2 + if: success() && github.event.number + id: fc + with: + issue-number: ${{ github.event.number }} + body-includes: '' + + - name: Create Comment + uses: peter-evans/create-or-update-comment@v2 + if: success() && github.event.number && steps.fc.outputs.comment-id == 0 + with: + issue-number: ${{ github.event.number }} + body: ${{ steps.get-comment-body.outputs.body }} + + - name: Update Comment + uses: peter-evans/create-or-update-comment@v2 + if: success() && github.event.number && steps.fc.outputs.comment-id != 0 + with: + issue-number: ${{ github.event.number }} + body: ${{ steps.get-comment-body.outputs.body }} + comment-id: ${{ steps.fc.outputs.comment-id }} + edit-mode: replace diff --git a/package.json b/package.json index 329f71c72e..8cc875803d 100644 --- a/package.json +++ b/package.json @@ -135,5 +135,11 @@ "typescript": "4.9.4", "typescript-plugin-css-modules": "^4.2.2", "webpack": "^5.88.2" + }, + "nextBundleAnalysis": { + "budget": null, + "budgetPercentIncreaseRed": 20, + "minimumChangeThreshold": 0, + "showDetails": true } } From a27250031bcb2c90121aa2770687a3f764e9a23c Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:32:07 +0100 Subject: [PATCH 17/17] fix: Lazy load mpc core kit (#2808) * fix: Lazy load mpc core kit * fix: Only instantiate service once, extract address book function * fix: Add skeleton for socialSigner loading state * chore: Update bundle-analyzer action to update comment instead of creating new one --- .github/workflows/nextjs_bundle_analysis.yml | 41 +----- .gitignore | 3 +- .../common/ConnectWallet/WalletDetails.tsx | 9 +- src/components/common/SocialSigner/index.tsx | 7 +- src/components/welcome/WelcomeLogin/index.tsx | 8 +- .../wallets/mpc/__tests__/useMPC.test.ts | 134 +++++------------ src/hooks/wallets/mpc/useMPC.ts | 139 +++++++++--------- .../wallets/mpc/useRehydrateSocialWallet.ts | 68 +++++++++ src/hooks/wallets/mpc/useSocialWallet.ts | 56 +------ src/pages/_app.tsx | 4 +- src/services/mpc/SocialLoginModule.ts | 24 +-- 11 files changed, 219 insertions(+), 274 deletions(-) create mode 100644 src/hooks/wallets/mpc/useRehydrateSocialWallet.ts diff --git a/.github/workflows/nextjs_bundle_analysis.yml b/.github/workflows/nextjs_bundle_analysis.yml index 2e9672b0df..1840f8d380 100644 --- a/.github/workflows/nextjs_bundle_analysis.yml +++ b/.github/workflows/nextjs_bundle_analysis.yml @@ -28,8 +28,6 @@ jobs: with: secrets: ${{ toJSON(secrets) }} - # Here's the first place where next-bundle-analysis' own script is used - # This step pulls the raw bundle stats for the current bundle - name: Analyze bundle run: npx -p nextjs-bundle-analysis report @@ -47,19 +45,6 @@ jobs: branch: ${{ github.event.pull_request.base.ref }} path: .next/analyze/base - # And here's the second place - this runs after we have both the current and - # base branch bundle stats, and will compare them to determine what changed. - # There are two configurable arguments that come from package.json: - # - # - budget: optional, set a budget (bytes) against which size changes are measured - # it's set to 350kb here by default, as informed by the following piece: - # https://infrequently.org/2021/03/the-performance-inequality-gap/ - # - # - red-status-percentage: sets the percent size increase where you get a red - # status indicator, defaults to 20% - # - # Either of these arguments can be changed or removed by editing the `nextBundleAnalysis` - # entry in your package.json file. - name: Compare with base branch bundle if: success() && github.event.number run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare @@ -73,26 +58,8 @@ jobs: echo "$(cat .next/analyze/__bundle_analysis_comment.txt)" >> $GITHUB_OUTPUT echo EOF >> $GITHUB_OUTPUT - - name: Find Comment - uses: peter-evans/find-comment@v2 - if: success() && github.event.number - id: fc - with: - issue-number: ${{ github.event.number }} - body-includes: '' - - - name: Create Comment - uses: peter-evans/create-or-update-comment@v2 - if: success() && github.event.number && steps.fc.outputs.comment-id == 0 - with: - issue-number: ${{ github.event.number }} - body: ${{ steps.get-comment-body.outputs.body }} - - - name: Update Comment - uses: peter-evans/create-or-update-comment@v2 - if: success() && github.event.number && steps.fc.outputs.comment-id != 0 + - name: Comment + uses: marocchino/sticky-pull-request-comment@v2 with: - issue-number: ${{ github.event.number }} - body: ${{ steps.get-comment-body.outputs.body }} - comment-id: ${{ steps.fc.outputs.comment-id }} - edit-mode: replace + header: next-bundle-analysis + message: ${{ steps.get-comment-body.outputs.body }} diff --git a/.gitignore b/.gitignore index 97916164f6..505387fd77 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,5 @@ yalc.lock /public/worker-*.js /public/workbox-*.js /public/workbox-*.js.map -/public/fallback* \ No newline at end of file +/public/fallback* +/public/*.js.LICENSE.txt \ No newline at end of file diff --git a/src/components/common/ConnectWallet/WalletDetails.tsx b/src/components/common/ConnectWallet/WalletDetails.tsx index 5f0442f50a..6a2ad4caf7 100644 --- a/src/components/common/ConnectWallet/WalletDetails.tsx +++ b/src/components/common/ConnectWallet/WalletDetails.tsx @@ -1,8 +1,13 @@ -import { Box, Divider, SvgIcon, Typography } from '@mui/material' +import { Box, Divider, Skeleton, SvgIcon, Typography } from '@mui/material' +import dynamic from 'next/dynamic' import type { ReactElement } from 'react' import LockIcon from '@/public/images/common/lock.svg' -import SocialSigner from '@/components/common/SocialSigner' + +const SocialSigner = dynamic(() => import('@/components/common/SocialSigner'), { + loading: () => , +}) + import WalletLogin from '@/components/welcome/WelcomeLogin/WalletLogin' const WalletDetails = ({ onConnect }: { onConnect: () => void }): ReactElement => { diff --git a/src/components/common/SocialSigner/index.tsx b/src/components/common/SocialSigner/index.tsx index c7f1138219..bef9d919b6 100644 --- a/src/components/common/SocialSigner/index.tsx +++ b/src/components/common/SocialSigner/index.tsx @@ -1,4 +1,7 @@ +import useSocialWallet from '@/hooks/wallets/mpc/useSocialWallet' +import { type ISocialWalletService } from '@/services/mpc/interfaces' import { Box, Button, LinearProgress, SvgIcon, Tooltip, Typography } from '@mui/material' +import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' import { useCallback, useContext, useMemo, useState } from 'react' import { PasswordRecovery } from '@/components/common/SocialSigner/PasswordRecovery' import GoogleLogo from '@/public/images/welcome/logo-google.svg' @@ -15,8 +18,6 @@ import { isSocialLoginWallet } from '@/services/mpc/SocialLoginModule' import { CGW_NAMES } from '@/hooks/wallets/consts' import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' import { TxModalContext } from '@/components/tx-flow' -import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' -import useSocialWallet from '@/hooks/wallets/mpc/useSocialWallet' import madProps from '@/utils/mad-props' import { asError } from '@/services/exceptions/utils' import ErrorMessage from '@/components/tx/ErrorMessage' @@ -41,7 +42,7 @@ const useIsSocialWalletEnabled = () => { } type SocialSignerLoginProps = { - socialWalletService: ReturnType + socialWalletService: ISocialWalletService | undefined wallet: ReturnType supportedChains: ReturnType isMPCLoginEnabled: ReturnType diff --git a/src/components/welcome/WelcomeLogin/index.tsx b/src/components/welcome/WelcomeLogin/index.tsx index ad768bf84a..9fcc869f44 100644 --- a/src/components/welcome/WelcomeLogin/index.tsx +++ b/src/components/welcome/WelcomeLogin/index.tsx @@ -1,9 +1,9 @@ -import SocialSigner from '@/components/common/SocialSigner' import { AppRoutes } from '@/config/routes' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' -import { Paper, SvgIcon, Typography, Divider, Link, Box } from '@mui/material' +import { Paper, SvgIcon, Typography, Divider, Link, Box, Skeleton } from '@mui/material' import SafeLogo from '@/public/images/logo-text.svg' +import dynamic from 'next/dynamic' import css from './styles.module.css' import { useRouter } from 'next/router' import WalletLogin from './WalletLogin' @@ -11,6 +11,10 @@ import { LOAD_SAFE_EVENTS, CREATE_SAFE_EVENTS } from '@/services/analytics/event import Track from '@/components/common/Track' import { trackEvent } from '@/services/analytics' +const SocialSigner = dynamic(() => import('@/components/common/SocialSigner'), { + loading: () => , +}) + const WelcomeLogin = () => { const router = useRouter() const isSocialLoginEnabled = useHasFeature(FEATURES.SOCIAL_LOGIN) diff --git a/src/hooks/wallets/mpc/__tests__/useMPC.test.ts b/src/hooks/wallets/mpc/__tests__/useMPC.test.ts index 2cdfd3f689..7c839a0cb8 100644 --- a/src/hooks/wallets/mpc/__tests__/useMPC.test.ts +++ b/src/hooks/wallets/mpc/__tests__/useMPC.test.ts @@ -1,8 +1,7 @@ import * as useOnboard from '@/hooks/wallets/useOnboard' import * as socialWalletOptions from '@/services/mpc/config' -import { renderHook, waitFor } from '@/tests/test-utils' -import { _getMPCCoreKitInstance, setMPCCoreKitInstance, useInitMPC } from '../useMPC' -import * as useChains from '@/hooks/useChains' +import { waitFor } from '@/tests/test-utils' +import { _getMPCCoreKitInstance, initMPC, setMPCCoreKitInstance } from '../useMPC' import { type ChainInfo, RPC_AUTHENTICATION } from '@safe-global/safe-gateway-typescript-sdk' import { hexZeroPad } from 'ethers/lib/utils' import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' @@ -63,53 +62,55 @@ class EventEmittingMockProvider { } } -describe('useInitMPC', () => { +describe('initMPC', () => { + const mockOnboard = { + state: { + get: () => ({ + wallets: [], + walletModules: [], + }), + }, + } as unknown as OnboardAPI + + const mockChain = { + chainId: '5', + chainName: 'Goerli', + blockExplorerUriTemplate: { + address: 'https://goerli.someprovider.io/{address}', + txHash: 'https://goerli.someprovider.io/{txHash}', + api: 'https://goerli.someprovider.io/', + }, + nativeCurrency: { + decimals: 18, + logoUri: 'https://logo.goerli.com', + name: 'Goerli ETH', + symbol: 'ETH', + }, + rpcUri: { + authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, + value: 'https://goerli.somerpc.io', + }, + } as unknown as ChainInfo + beforeEach(() => { jest.resetAllMocks() jest.spyOn(socialWalletOptions, 'isSocialWalletOptions').mockReturnValue(true) }) + it('should set the coreKit if user is not logged in yet', async () => { - const connectWalletSpy = jest.fn().mockImplementation(() => Promise.resolve()) - jest.spyOn(useOnboard, 'connectWallet').mockImplementation(connectWalletSpy) + jest.spyOn(useOnboard, 'connectWallet').mockImplementation(() => Promise.resolve(undefined)) jest.spyOn(useOnboard, 'getConnectedWallet').mockReturnValue(null) - jest.spyOn(useOnboard, 'default').mockReturnValue({ - state: { - get: () => ({ - wallets: [], - walletModules: [], - }), - }, - } as unknown as OnboardAPI) - jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ - chainId: '5', - chainName: 'Goerli', - blockExplorerUriTemplate: { - address: 'https://goerli.someprovider.io/{address}', - txHash: 'https://goerli.someprovider.io/{txHash}', - api: 'https://goerli.someprovider.io/', - }, - nativeCurrency: { - decimals: 18, - logoUri: 'https://logo.goerli.com', - name: 'Goerli ETH', - symbol: 'ETH', - }, - rpcUri: { - authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, - value: 'https://goerli.somerpc.io', - }, - } as unknown as ChainInfo) const mockWeb3AuthMpcCoreKit = jest.spyOn(require('@web3auth/mpc-core-kit'), 'Web3AuthMPCCoreKit') mockWeb3AuthMpcCoreKit.mockImplementation(() => { return new MockMPCCoreKit(COREKIT_STATUS.INITIALIZED, null) }) - renderHook(() => useInitMPC()) + await initMPC(mockChain, mockOnboard) await waitFor(() => { expect(_getMPCCoreKitInstance()).toBeDefined() - expect(connectWalletSpy).not.toBeCalled() + expect(useOnboard.connectWallet).not.toBeCalled() }) }) @@ -117,33 +118,6 @@ describe('useInitMPC', () => { const connectWalletSpy = jest.fn().mockImplementation(() => Promise.resolve()) jest.spyOn(useOnboard, 'connectWallet').mockImplementation(connectWalletSpy) jest.spyOn(useOnboard, 'getConnectedWallet').mockReturnValue(null) - jest.spyOn(useOnboard, 'default').mockReturnValue({ - state: { - get: () => ({ - wallets: [], - walletModules: [], - }), - }, - } as unknown as OnboardAPI) - jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ - chainId: '5', - chainName: 'Goerli', - blockExplorerUriTemplate: { - address: 'https://goerli.someprovider.io/{address}', - txHash: 'https://goerli.someprovider.io/{txHash}', - api: 'https://goerli.someprovider.io/', - }, - nativeCurrency: { - decimals: 18, - logoUri: 'https://logo.goerli.com', - name: 'Goerli ETH', - symbol: 'ETH', - }, - rpcUri: { - authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, - value: 'https://goerli.somerpc.io', - }, - } as unknown as ChainInfo) const mockWeb3AuthMpcCoreKit = jest.spyOn(require('@web3auth/mpc-core-kit'), 'Web3AuthMPCCoreKit') const mockProvider = jest.fn() @@ -151,7 +125,7 @@ describe('useInitMPC', () => { return new MockMPCCoreKit(COREKIT_STATUS.LOGGED_IN, mockProvider as unknown as MPCProvider) }) - renderHook(() => useInitMPC()) + await initMPC(mockChain, mockOnboard) await waitFor(() => { expect(connectWalletSpy).toBeCalled() @@ -160,41 +134,13 @@ describe('useInitMPC', () => { }) it('should copy event handlers and emit chainChanged if the current chain is updated', async () => { - const connectWalletSpy = jest.fn().mockImplementation(() => Promise.resolve()) - jest.spyOn(useOnboard, 'connectWallet').mockImplementation(connectWalletSpy) + jest.spyOn(useOnboard, 'connectWallet').mockImplementation(() => Promise.resolve(undefined)) jest.spyOn(useOnboard, 'getConnectedWallet').mockReturnValue({ address: hexZeroPad('0x1', 20), label: ONBOARD_MPC_MODULE_LABEL, chainId: '1', provider: {} as unknown as EIP1193Provider, }) - jest.spyOn(useOnboard, 'default').mockReturnValue({ - state: { - get: () => ({ - wallets: [], - walletModules: [], - }), - }, - } as unknown as OnboardAPI) - jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ - chainId: '5', - chainName: 'Goerli', - blockExplorerUriTemplate: { - address: 'https://goerli.someprovider.io/{address}', - txHash: 'https://goerli.someprovider.io/{txHash}', - api: 'https://goerli.someprovider.io/', - }, - nativeCurrency: { - decimals: 18, - logoUri: 'https://logo.goerli.com', - name: 'Goerli ETH', - symbol: 'ETH', - }, - rpcUri: { - authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, - value: 'https://goerli.somerpc.io', - }, - } as unknown as ChainInfo) const mockWeb3AuthMpcCoreKit = jest.spyOn(require('@web3auth/mpc-core-kit'), 'Web3AuthMPCCoreKit') const mockChainChangedListener = jest.fn() @@ -215,12 +161,12 @@ describe('useInitMPC', () => { return new MockMPCCoreKit(COREKIT_STATUS.LOGGED_IN, mockProvider as unknown as MPCProvider) }) - renderHook(() => useInitMPC()) + await initMPC(mockChain, mockOnboard) await waitFor(() => { expect(mockChainChangedListener).toHaveBeenCalledWith('0x5') expect(_getMPCCoreKitInstance()).toBeDefined() - expect(connectWalletSpy).not.toBeCalled() + expect(useOnboard.connectWallet).not.toBeCalled() }) }) }) diff --git a/src/hooks/wallets/mpc/useMPC.ts b/src/hooks/wallets/mpc/useMPC.ts index b0b9d8544a..c73a8e2fee 100644 --- a/src/hooks/wallets/mpc/useMPC.ts +++ b/src/hooks/wallets/mpc/useMPC.ts @@ -1,88 +1,83 @@ -import { useEffect } from 'react' +import { IS_PRODUCTION } from '@/config/constants' +import { connectWallet, getConnectedWallet } from '@/hooks/wallets/useOnboard' import ExternalStore from '@/services/ExternalStore' -import { COREKIT_STATUS, Web3AuthMPCCoreKit, WEB3AUTH_NETWORK } from '@web3auth/mpc-core-kit' +import { SOCIAL_WALLET_OPTIONS } from '@/services/mpc/config' +import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' +import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' +import { type OnboardAPI } from '@web3-onboard/core' import { CHAIN_NAMESPACES } from '@web3auth/base' - -import { useCurrentChain } from '@/hooks/useChains' +import type { Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit' +import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' import { getRpcServiceUrl } from '../web3' -import useOnboard, { connectWallet, getConnectedWallet } from '@/hooks/wallets/useOnboard' -import { useInitSocialWallet } from './useSocialWallet' -import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' -import { isSocialWalletOptions, SOCIAL_WALLET_OPTIONS } from '@/services/mpc/config' -import { IS_PRODUCTION } from '@/config/constants' const { getStore, setStore, useStore } = new ExternalStore() -export const useInitMPC = () => { - const chain = useCurrentChain() - const onboard = useOnboard() - useInitSocialWallet() +export const initMPC = async (chain: ChainInfo, onboard: OnboardAPI) => { + const chainConfig = { + chainId: `0x${Number(chain.chainId).toString(16)}`, + chainNamespace: CHAIN_NAMESPACES.EIP155, + rpcTarget: getRpcServiceUrl(chain.rpcUri), + displayName: chain.chainName, + blockExplorer: new URL(chain.blockExplorerUriTemplate.address).origin, + ticker: chain.nativeCurrency.symbol, + tickerName: chain.nativeCurrency.name, + } - useEffect(() => { - if (!chain || !onboard || !isSocialWalletOptions(SOCIAL_WALLET_OPTIONS)) { - return - } + const currentInstance = getStore() + let previousChainChangedListeners: Function[] = [] + if (currentInstance?.provider) { + // We are already connected. We copy onboards event listener for the chainChanged event to propagate a potentially new chainId + const oldProvider = currentInstance.provider + previousChainChangedListeners = oldProvider.listeners('chainChanged') + } - const chainConfig = { - chainId: `0x${Number(chain.chainId).toString(16)}`, - chainNamespace: CHAIN_NAMESPACES.EIP155, - rpcTarget: getRpcServiceUrl(chain.rpcUri), - displayName: chain.chainName, - blockExplorer: new URL(chain.blockExplorerUriTemplate.address).origin, - ticker: chain.nativeCurrency.symbol, - tickerName: chain.nativeCurrency.name, - } + const { Web3AuthMPCCoreKit, WEB3AUTH_NETWORK } = await import('@web3auth/mpc-core-kit') - const currentInstance = getStore() - let previousChainChangedListeners: Function[] = [] - if (currentInstance?.provider) { - // We are already connected. We copy onboards event listener for the chainChanged event to propagate a potentially new chainId - const oldProvider = currentInstance.provider - previousChainChangedListeners = oldProvider.listeners('chainChanged') - } + const web3AuthCoreKit = new Web3AuthMPCCoreKit({ + web3AuthClientId: SOCIAL_WALLET_OPTIONS.web3AuthClientId, + // Available networks are "sapphire_devnet", "sapphire_mainnet" + web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, + baseUrl: `${window.location.origin}/`, + uxMode: 'popup', + enableLogging: !IS_PRODUCTION, + //@ts-ignore + chainConfig, + manualSync: true, + hashedFactorNonce: 'safe-global-sfa-nonce', + }) - const web3AuthCoreKit = new Web3AuthMPCCoreKit({ - web3AuthClientId: SOCIAL_WALLET_OPTIONS.web3AuthClientId, - // Available networks are "sapphire_devnet", "sapphire_mainnet" - web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, - baseUrl: `${window.location.origin}/`, - uxMode: 'popup', - enableLogging: !IS_PRODUCTION, - chainConfig, - manualSync: true, - hashedFactorNonce: 'safe-global-sfa-nonce', - }) + return web3AuthCoreKit + .init() + .then(() => { + setStore(web3AuthCoreKit) + // If rehydration was successful, connect to onboard + if (web3AuthCoreKit.status !== COREKIT_STATUS.LOGGED_IN || !web3AuthCoreKit.provider) { + return web3AuthCoreKit + } - web3AuthCoreKit - .init() - .then(() => { - setStore(web3AuthCoreKit) - // If rehydration was successful, connect to onboard - if (web3AuthCoreKit.status !== COREKIT_STATUS.LOGGED_IN || !web3AuthCoreKit.provider) { - return - } - const connectedWallet = getConnectedWallet(onboard.state.get().wallets) - if (!connectedWallet) { - connectWallet(onboard, { - autoSelect: { - label: ONBOARD_MPC_MODULE_LABEL, - disableModals: true, - }, - }).catch((reason) => console.error('Error connecting to MPC module:', reason)) - } else { - const newProvider = web3AuthCoreKit.provider + const connectedWallet = getConnectedWallet(onboard.state.get().wallets) + if (!connectedWallet) { + connectWallet(onboard, { + autoSelect: { + label: ONBOARD_MPC_MODULE_LABEL, + disableModals: true, + }, + }).catch((reason) => console.error('Error connecting to MPC module:', reason)) + } else { + const newProvider = web3AuthCoreKit.provider - // To propagate the changedChain we disconnect and connect - if (previousChainChangedListeners.length > 0 && newProvider) { - previousChainChangedListeners.forEach((previousListener) => - newProvider.addListener('chainChanged', (...args: []) => previousListener(...args)), - ) - newProvider.emit('chainChanged', `0x${Number(chainConfig.chainId).toString(16)}`) - } + // To propagate the changedChain we disconnect and connect + if (previousChainChangedListeners.length > 0 && newProvider) { + previousChainChangedListeners.forEach((previousListener) => + newProvider.addListener('chainChanged', (...args: []) => previousListener(...args)), + ) + newProvider.emit('chainChanged', `0x${Number(chainConfig.chainId).toString(16)}`) } - }) - .catch((error) => console.error(error)) - }, [chain, onboard]) + } + + return web3AuthCoreKit + }) + .catch((error) => console.error(error)) } export const _getMPCCoreKitInstance = getStore diff --git a/src/hooks/wallets/mpc/useRehydrateSocialWallet.ts b/src/hooks/wallets/mpc/useRehydrateSocialWallet.ts new file mode 100644 index 0000000000..799d7f2036 --- /dev/null +++ b/src/hooks/wallets/mpc/useRehydrateSocialWallet.ts @@ -0,0 +1,68 @@ +import useAddressBook from '@/hooks/useAddressBook' +import useChainId from '@/hooks/useChainId' +import { useCurrentChain } from '@/hooks/useChains' +import useOnboard, { connectWallet } from '@/hooks/wallets/useOnboard' +import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' +import { useAppDispatch } from '@/store' +import { upsertAddressBookEntry } from '@/store/addressBookSlice' +import { type WalletState } from '@web3-onboard/core' +import { type UserInfo } from '@web3auth/mpc-core-kit' +import { useCallback, useEffect } from 'react' +import { checksumAddress } from '@/utils/addresses' + +const useRehydrateSocialWallet = () => { + const chain = useCurrentChain() + const onboard = useOnboard() + const currentChainId = useChainId() + const addressBook = useAddressBook() + const dispatch = useAppDispatch() + + const updateAddressBook = useCallback( + (userInfo: UserInfo | undefined, wallets: WalletState[] | undefined | void) => { + if (!userInfo || !wallets || !currentChainId || wallets.length === 0) return + + const address = wallets[0].accounts[0]?.address + if (address) { + const signerAddress = checksumAddress(address) + if (addressBook[signerAddress] === undefined) { + const email = userInfo.email + dispatch(upsertAddressBookEntry({ address: signerAddress, chainId: currentChainId, name: email })) + } + } + }, + [addressBook, currentChainId, dispatch], + ) + + useEffect(() => { + if (!chain || !onboard) return + + const rehydrate = async () => { + const { initMPC } = await import('./useMPC') + const { initSocialWallet } = await import('./useSocialWallet') + const mpcCoreKit = await initMPC(chain, onboard) + + if (!mpcCoreKit) return + + const socialWalletService = await initSocialWallet(mpcCoreKit) + + const onConnect = async () => { + const wallets = await connectWallet(onboard, { + autoSelect: { + label: ONBOARD_MPC_MODULE_LABEL, + disableModals: true, + }, + }).catch((reason) => console.error('Error connecting to MPC module:', reason)) + + // If the signer is not in the address book => add the user's email as name + const userInfo = socialWalletService?.getUserInfo() + updateAddressBook(userInfo, wallets) + } + + socialWalletService.setOnConnect(onConnect) + } + + void rehydrate() + }, [chain, onboard, updateAddressBook]) +} + +export default useRehydrateSocialWallet diff --git a/src/hooks/wallets/mpc/useSocialWallet.ts b/src/hooks/wallets/mpc/useSocialWallet.ts index d98998ffdd..8db21735d6 100644 --- a/src/hooks/wallets/mpc/useSocialWallet.ts +++ b/src/hooks/wallets/mpc/useSocialWallet.ts @@ -1,59 +1,15 @@ -import useAddressBook from '@/hooks/useAddressBook' -import useChainId from '@/hooks/useChainId' import ExternalStore from '@/services/ExternalStore' import type { ISocialWalletService } from '@/services/mpc/interfaces' -import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' -import SocialWalletService from '@/services/mpc/SocialWalletService' -import { useAppDispatch } from '@/store' -import { upsertAddressBookEntry } from '@/store/addressBookSlice' -import { checksumAddress } from '@/utils/addresses' -import { useCallback, useEffect } from 'react' -import useOnboard, { connectWallet } from '../useOnboard' -import useMpc from './useMPC' +import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit' const { getStore, setStore, useStore } = new ExternalStore() -export const useInitSocialWallet = () => { - const mpcCoreKit = useMpc() - const onboard = useOnboard() - const addressBook = useAddressBook() - const currentChainId = useChainId() - const dispatch = useAppDispatch() - const socialWalletService = useStore() +export const initSocialWallet = async (mpcCoreKit: Web3AuthMPCCoreKit) => { + const SocialWalletService = (await import('@/services/mpc/SocialWalletService')).default + const socialWalletService = new SocialWalletService(mpcCoreKit) + setStore(socialWalletService) - const onConnect = useCallback(async () => { - if (!onboard || !socialWalletService) return - - const wallets = await connectWallet(onboard, { - autoSelect: { - label: ONBOARD_MPC_MODULE_LABEL, - disableModals: true, - }, - }).catch((reason) => console.error('Error connecting to MPC module:', reason)) - - // If the signer is not in the address book => add the user's email as name - const userInfo = socialWalletService.getUserInfo() - if (userInfo && wallets && currentChainId && wallets.length > 0) { - const address = wallets[0].accounts[0]?.address - if (address) { - const signerAddress = checksumAddress(address) - if (addressBook[signerAddress] === undefined) { - const email = userInfo.email - dispatch(upsertAddressBookEntry({ address: signerAddress, chainId: currentChainId, name: email })) - } - } - } - }, [addressBook, currentChainId, dispatch, onboard, socialWalletService]) - - useEffect(() => { - socialWalletService?.setOnConnect(onConnect) - }, [onConnect, socialWalletService]) - - useEffect(() => { - if (mpcCoreKit) { - setStore(new SocialWalletService(mpcCoreKit)) - } - }, [mpcCoreKit]) + return socialWalletService } export const getSocialWalletService = getStore diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index e7b3d6d3d7..65dcbf5fe9 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,3 +1,4 @@ +import useRehydrateSocialWallet from '@/hooks/wallets/mpc/useRehydrateSocialWallet' import PasswordRecoveryModal from '@/services/mpc/PasswordRecoveryModal' import Sentry from '@/services/sentry' // needs to be imported first import type { ReactNode } from 'react' @@ -38,7 +39,6 @@ import useSafeMessageNotifications from '@/hooks/messages/useSafeMessageNotifica import useSafeMessagePendingStatuses from '@/hooks/messages/useSafeMessagePendingStatuses' import useChangedValue from '@/hooks/useChangedValue' import { TxModalProvider } from '@/components/tx-flow' -import { useInitMPC } from '@/hooks/wallets/mpc/useMPC' import { WalletConnectProvider } from '@/services/walletconnect/WalletConnectContext' import useABTesting from '@/services/tracking/useAbTesting' import { AbTest } from '@/services/tracking/abTesting' @@ -65,7 +65,7 @@ const InitApp = (): null => { useTxTracking() useSafeMsgTracking() useBeamer() - useInitMPC() + useRehydrateSocialWallet() useABTesting(AbTest.HUMAN_DESCRIPTION) return null diff --git a/src/services/mpc/SocialLoginModule.ts b/src/services/mpc/SocialLoginModule.ts index 6e34ce89d8..84052dfbed 100644 --- a/src/services/mpc/SocialLoginModule.ts +++ b/src/services/mpc/SocialLoginModule.ts @@ -1,14 +1,9 @@ -import { _getMPCCoreKitInstance } from '@/hooks/wallets/mpc/useMPC' -import { getSocialWalletService } from '@/hooks/wallets/mpc/useSocialWallet' import { getWeb3ReadOnly } from '@/hooks/wallets/web3' -import * as PasswordRecoveryModal from '@/services/mpc/PasswordRecoveryModal' import { FEATURES, hasFeature } from '@/utils/chains' import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' import { type WalletInit, ProviderRpcError } from '@web3-onboard/common' import { type EIP1193Provider } from '@web3-onboard/core' -import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' - -const getMPCProvider = () => _getMPCCoreKitInstance()?.provider +import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit' const assertDefined = (mpcProvider: T | undefined) => { if (!mpcProvider) { @@ -23,9 +18,9 @@ export const isSocialLoginWallet = (walletLabel: string | undefined) => { return walletLabel === ONBOARD_MPC_MODULE_LABEL } -const getConnectedAccounts = async () => { +const getConnectedAccounts = async (provider: typeof Web3AuthMPCCoreKit.prototype.provider | undefined) => { try { - const web3 = assertDefined(getMPCProvider()) + const web3 = assertDefined(provider) return web3.request({ method: 'eth_accounts' }) } catch (e) { throw new ProviderRpcError({ @@ -50,6 +45,13 @@ function MpcModule(chain: ChainInfo): WalletInit { label: ONBOARD_MPC_MODULE_LABEL, getIcon: async () => (await import('./icon')).default, getInterface: async () => { + const { _getMPCCoreKitInstance } = await import('@/hooks/wallets/mpc/useMPC') + const { getSocialWalletService } = await import('@/hooks/wallets/mpc/useSocialWallet') + const { COREKIT_STATUS } = await import('@web3auth/mpc-core-kit') + const { open } = await import('./PasswordRecoveryModal') + + const getMPCProvider = () => _getMPCCoreKitInstance()?.provider + const provider: EIP1193Provider = { on: (event, listener) => { const web3 = assertDefined(getMPCProvider()) @@ -85,11 +87,11 @@ function MpcModule(chain: ChainInfo): WalletInit { const status = await socialWalletService.loginAndCreate() if (status === COREKIT_STATUS.REQUIRED_SHARE) { - PasswordRecoveryModal.open(() => { - getConnectedAccounts().then(resolve).catch(reject) + open(() => { + getConnectedAccounts(getMPCProvider()).then(resolve).catch(reject) }) } else { - getConnectedAccounts().then(resolve).catch(reject) + getConnectedAccounts(getMPCProvider()).then(resolve).catch(reject) } } return